С# оптимизирует код автоматически в зацикленных/лямбда-операторах?

В Javascript, например, настоятельно рекомендуется размещать вызовы функций за пределами циклов для лучшей производительности:

var id = someIdType.ToString();
someList.Where(a => a.id == id) ...

Как насчет С#? В том же случае или использует ли компилятор/среда выполнения внутреннюю оптимизацию/кэширование?

someList.Where(a => a.id == someIdType.ToString()) ...

Вероятно, вопрос о нобе и был задан раньше, но не может найти ссылку.

Ответы

Ответ 1

Код С#:

List<string> list = new List<string>();
list.Where(a => a == typeof(String).ToString());

Lambda выражение в MSIL, Конфигурация отладки:

.method private hidebysig static bool  '<Main>b__0'(string a) cil managed
{
  .custom instance void     [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init ([0] bool CS$1$0000)
  IL_0000:  ldarg.0
  IL_0001:  ldtoken    [mscorlib]System.String
  IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_000b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0010:  call       bool [mscorlib]System.String::op_Equality(string,
                                                             string)
  IL_0015:  stloc.0
  IL_0016:  br.s       IL_0018
  IL_0018:  ldloc.0
  IL_0019:  ret
} // end of method Program::'<Main>b__0'

Лямбда-выражение в MSIL, Конфигурация выпуска:

.method private hidebysig static bool  '<Main>b__0'(string a) cil managed
{
  .custom instance void     [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldtoken    [mscorlib]System.String
  IL_0006:  call       class [mscorlib]System.Type     [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_000b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0010:  call       bool [mscorlib]System.String::op_Equality(string,
                                                             string)
  IL_0015:  ret
} // end of method Program::'<Main>b__0'

Обе версии называют typeof(String).ToString()), эта лямбда вызывается на каждой итерации. Никакой оптимизации на уровне IL, компиляция JIT ничего не добавит. Причина в том, что функция может иметь побочные эффекты.

Ответ 2

Лямбда будет выполнена для каждого элемента списка. Следовательно, код someIdType.ToString() будет выполняться для каждого элемента. Я не думаю, что компилятор или среда выполнения кэшируют его для вас. (AFAIK someIdType будет зафиксирован в закрытии, но не .ToString())

EDIT: Первоначальный вопрос касался только "Делает?", Но не "Почему?", Но все же есть несколько комментариев и других ответов, которые пытаются ответить/продемонстрировать "Почему?".

Учитывая большой интерес к "Почему?" Я редактирую свой ответ, чтобы указать мою версию "Почему?". т.е. если вы посмотрите спецификацию С#, для любого из соответствующих сценариев спецификация говорит о capturing the variable.. не capturing the expression. Вот почему компилятор ведет себя так, как он делает. Потому что его не в спецификации. Почему это не в спецификации, это то, на что может ответить команда разработчиков С#. Отдых - это спекуляция, части или все из которых могут иметь или не иметь достоинства, если следует рассмотреть feature of capturing expressions.