Каково время жизни делегата, созданного лямбда-выражением в C #? - PullRequest
42 голосов
/ 08 июня 2011

Лямбды хороши, поскольку они предлагают краткость и локальность и дополнительную форму инкапсуляции .Вместо того, чтобы писать функции, которые используются только один раз, вы можете использовать лямбду.

Интересно, как они работают, я интуитивно предположил, что они , вероятно, созданы только один раз .Это вдохновило меня на создание решения, которое позволит ограничить область видимости члена класса за пределами одной конкретной областью, используя лямбду в качестве идентификатора области, в которой он был создан.

Эта реализация работает, хотя, возможно, излишне (все еще исследует ее), подтверждая мое предположение, чтобы быть верным.

Небольшой пример:

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

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

Ответы [ 5 ]

29 голосов
/ 08 июня 2011

Исходя из вашего вопроса здесь и вашего комментария к ответу Джона, я думаю, что вы путаете несколько вещей. Чтобы убедиться, что это ясно:

  • Метод , который поддерживает делегат для данной лямбды, всегда одинаков.
  • Метод , который поддерживает делегат для "одной и той же" лямбды, которая встречается в лексическом смысле дважды, - это , допускается, чтобы был таким же, но на практике не то же самое в нашей реализации.
  • Экземпляр делегата , который создается для данной лямбды, может или не может быть одинаковым, в зависимости от того, насколько умен его компилятор в отношении кеширования.

Так что если у вас есть что-то вроде:

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;

Однако компилятору разрешено определять, что две лямбды идентичны, и генерировать

static void MyAction1() {}
static Action ActionCache1 = null;
...
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
Action a1 = ActionCache1;
Action a2 = ActionCache1;

Теперь понятно?

29 голосов
/ 08 июня 2011

Это не гарантировано в любом случае.

Из того, что я помню о текущей реализации MS:

  • Лямбда-выражение, которое не фиксирует любых переменных, статически кэшируется
  • Лямбда-выражение, которое фиксирует только «это» , может быть записано для каждого экземпляра, но не
  • Лямбда-выражение, которое захватывает локальную переменную, не может быть кэшировано
  • Два лямбда-выражения с одинаковым текстом программы не являются псевдонимами; в некоторых случаях они могут быть, но разработка ситуаций, в которых они могут быть, будет очень сложной
  • РЕДАКТИРОВАТЬ: Как отмечает Эрик в комментариях, вам также нужно учитывать набрать аргументов, перехваченных для универсальных методов.

РЕДАКТИРОВАТЬ: Соответствующий текст спецификации C # 4 находится в разделе 6.5.1:

Преобразованию семантически идентичных анонимных функций с одинаковым (возможно, пустым) набором захваченных экземпляров внешних переменных в одинаковые типы делегатов разрешено (но не обязательно) возвращать один и тот же экземпляр делегата. Термин семантически идентичный используется здесь для обозначения того, что выполнение анонимных функций во всех случаях будет давать одинаковые эффекты при одинаковых аргументах.

4 голосов
/ 08 июня 2011

Никаких гарантий.

Небольшая демонстрация:

Action GetAction()
{
    return () => Console.WriteLine("foo");
}

Позвоните дважды, сделайте ReferenceEquals(a,b), и вы получите true

Action GetAction()
{
    var foo = "foo";
    return () => Console.WriteLine(foo);
}

Вызовите это дважды, сделайте ReferenceEquals(a,b), и вы получите false

3 голосов
/ 08 июня 2011

Я вижу, как Скит запрыгнул, когда я отвечал, так что я не буду это понимать. Одна вещь, которую я хотел бы предложить, чтобы лучше понять, как вы используете вещи, это познакомиться с инструментами обратного проектирования и IL. Возьмите пример кода (ов) и обратный инжиниринг в IL. Он даст вам большое количество информации о том, как работает код.

1 голос
/ 08 июня 2011

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

По крайней мере, я проголосовал за вас, так что, надеюсь, кто-то может дать вам академический ответ, который вы ищете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...