Подтвердить что ты не робот

Каково время жизни делегата, созданного лямбдой в С#?

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()}}
    }
}

Будет ли лямбда когда-либо возвращать новый экземпляр, или он всегда будет неизменным?

4b9b3361

Ответ 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

Хороший вопрос. У меня нет "академического ответа", более практического ответа: я мог видеть, что компилятор оптимизирует двоичный файл, чтобы использовать один и тот же экземпляр, но я бы никогда не писал код, который предполагает, что он "гарантирован" будет одним и тем же экземпляром,

Я, по крайней мере, поддержал вас, поэтому, надеюсь, кто-то может дать вам академический ответ, который вы ищете.