Каково время жизни делегата, созданного лямбдой в С#?
Lambdas хороши, поскольку они предлагают краткость и локальность и дополнительную форму инкапсуляции. Вместо того, чтобы писать функции, которые используются только после использования лямбда.
Не зная, как они работали, я интуитивно понял, что они , возможно, созданы только один раз. Это побудило меня создать решение, позволяющее ограничить объем члена класса за пределами частного до одной конкретной области, используя лямбда в качестве идентификатора области, которую она создала в.
Эта реализация работает, хотя, возможно, переполняет (все еще исследует ее), доказывая, что мое предположение является правильным.
Меньший пример:
class SomeClass
{
public void Bleh()
{
Action action = () => {};
}
public void CallBleh()
{
Bleh(); // `action` == {Method = {Void <SomeClass>b__0()}}
Bleh(); // `action` still == {Method = {Void <SomeClass>b__0()}}
}
}
Будет ли лямбда когда-либо возвращать новый экземпляр, или он всегда будет неизменным?
Ответы
Ответ 1
Основываясь на вашем вопросе здесь и вашем комментарии к Джону, я думаю, что вы путаете несколько вещей. Чтобы убедиться, что это понятно:
- Метод, который поддерживает делегат для данной лямбда, всегда одинаков.
- Метод, который поддерживает делегат для "той же" лямбды, которая появляется лексически дважды, может быть одинаковой, но на практике это не то же самое в нашей реализации.
- Экземпляр делегата, созданный для данной лямбды, может быть или не всегда совпадать, в зависимости от того, насколько умным является компилятор для кэширования.
Итак, если у вас есть что-то вроде:
for(i = 0; i < 10; ++i)
M( ()=>{} )
то каждый раз, когда вызывается M, вы получаете тот же экземпляр делегата, потому что компилятор умный и генерирует
static void MyAction() {}
static Action DelegateCache = null;
...
for(i = 0; i < 10; ++i)
{
if (C.DelegateCache == null) C.DelegateCache = new Action ( C.MyAction )
M(C.DelegateCache);
}
Если у вас
for(i = 0; i < 10; ++i)
M( ()=>{this.Bar();} )
тогда компилятор генерирует
void MyAction() { this.Bar(); }
...
for(i = 0; i < 10; ++i)
{
M(new Action(this.MyAction));
}
Вы получаете новый делегат каждый раз, используя тот же метод.
Компилятору разрешено (но на самом деле не в это время) генерировать
void MyAction() { this.Bar(); }
Action DelegateCache = null;
...
for(i = 0; i < 10; ++i)
{
if (this.DelegateCache == null) this.DelegateCache = new Action ( this.MyAction )
M(this.DelegateCache);
}
В этом случае вы всегда получите один и тот же экземпляр делегата, если это возможно, и каждый делегат будет поддерживаться одним и тем же методом.
Если у вас
Action a1 = ()=>{};
Action a2 = ()=>{};
Тогда на практике компилятор генерирует это как
static void MyAction1() {}
static void MyAction2() {}
static Action ActionCache1 = null;
static Action ActionCache2 = null;
...
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
Action a1 = ActionCache1;
if (ActionCache2 == null) ActionCache2 = new Action(MyAction2);
Action a2 = ActionCache2;
Однако компилятору разрешено обнаруживать, что два lambdas идентичны и генерируют
static void MyAction1() {}
static Action ActionCache1 = null;
...
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
Action a1 = ActionCache1;
Action a2 = ActionCache1;
Теперь ясно?
Ответ 2
Это не гарантируется в любом случае.
Из того, что я помню о текущей реализации MS:
- Лямбда-выражение, которое не фиксирует какие-либо переменные, статически статично
- Лямбда-выражение, которое захватывает только "this", может быть захвачено на основе каждого экземпляра, но не
- Лямбда-выражение, которое фиксирует локальную переменную, не может быть кэшировано
- Два лямбда-выражения, которые имеют один и тот же текст программы, не сглажены; в некоторых случаях они могут быть, но разработка ситуаций, в которых они могут быть, будет очень сложной.
- EDIT: Как отмечает Эрик в комментариях, вам также необходимо рассмотреть аргументы типа, которые будут использоваться для общих методов.
EDIT: соответствующий текст спецификации С# 4 приведен в разделе 6.5.1:
Разрешения семантически идентичных анонимных функций с тем же (возможно, пустым) набором захваченных внешних экземпляров переменной для тех же типов делегатов разрешены (но не обязательно) для возврата одного и того же экземпляра делегата. Термин семантически идентичный используется здесь, чтобы означать, что выполнение анонимных функций будет во всех случаях производить одинаковые эффекты с учетом тех же аргументов.
Ответ 3
Нет гарантий.
Быстрая демонстрация:
Action GetAction()
{
return () => Console.WriteLine("foo");
}
Вызовите это дважды, сделайте ReferenceEquals(a,b)
, и вы получите true
Action GetAction()
{
var foo = "foo";
return () => Console.WriteLine(foo);
}
Назовите это дважды, сделайте ReferenceEquals(a,b)
, и вы получите false
Ответ 4
Я вижу, что Скит вскочил, когда я отвечал, поэтому я не буду расстраивать этот момент. Одна вещь, которую я хотел бы предложить, чтобы лучше понять, как вы используете вещи, - это ознакомиться с инструментами обратного проектирования и IL. Возьмите образец кода, о котором идет речь, и обратитесь к ИЛ. Это даст вам большой объем информации о том, как работает код.
Ответ 5
Хороший вопрос. У меня нет "академического ответа", более практического ответа: я мог видеть, что компилятор оптимизирует двоичный файл, чтобы использовать один и тот же экземпляр, но я бы никогда не писал код, который предполагает, что он "гарантирован" будет одним и тем же экземпляром,
Я, по крайней мере, поддержал вас, поэтому, надеюсь, кто-то может дать вам академический ответ, который вы ищете.