Анонимные методы и лямбда-выражения [дубликат]

этот вопрос уже есть ответ здесь:

может ли кто - нибудь предоставить краткое различие между анонимным методом и лямбда-выражениями?

использование anonymous метод:

private void DoSomeWork()
{
    if (textBox1.InvokeRequired)
    {
        //textBox1.Invoke((Action)(() => textBox1.Text = "test"));
        textBox1.Invoke((Action)delegate { textBox1.Text = "test"; });
    }
}

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

Я хорошо знаю, что строго типизированный делегат, такой как follow

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName)

достаточно в качестве параметра типа System.Delegate, но идея анонимного метода довольно нова для меня.

3 ответов


что is анонимный метод? Это действительно анонимно? У него есть название? Все хорошие вопросы, поэтому давайте начнем с них и будем работать до лямбда-выражений по мере продвижения.

при этом:

public void TestSomething()
{
    Test(delegate { Debug.WriteLine("Test"); });
}

что на самом деле происходит?

компилятор сначала решает взять "тело" метода, которое является следующим:

Debug.WriteLine("Test");

и отделить это в метод.

два вопроса компилятор теперь должен ответить:

  1. куда я должен поместить метод?
  2. как должна выглядеть подпись метода?

второй вопрос легко ответить. The delegate { часть ответы. Метод не принимает параметров (между delegate и {), и поскольку нас не волнует его имя (отсюда и "анонимная" часть), мы можем объявить метод как таковой:

public void SomeOddMethod()
{
    Debug.WriteLine("Test");
}

но почему он сделал все это?

давайте посмотрим, что такое делегат, например Action на самом деле.

делегат есть, если мы на мгновение игнорируем тот факт, что делегаты в .NET фактически связаны списком несколько одиночные "делегаты", ссылка (указатель) на две вещи:

  1. экземпляр объекта
  2. метод на этом экземпляре объекта

таким образом, с этим знанием первый фрагмент кода может быть фактически переписан как это:

public void TestSomething()
{
    Test(new Action(this.SomeOddMethod));
}

private void SomeOddMethod()
{
    Debug.WriteLine("Test");
}

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

например, рассмотрим, был ли приведенный выше код частью действительно огромного объекта, но объекта, который живет только временно. Также рассматривать это Test будет хранить этот делегат где-то, где он будет жить в течение длительного времени. Это "долгое время" также будет связано с жизнью этого огромного объекта, сохраняя ссылку на это долгое время, вероятно, не хорошо.

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

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

public void TestSomething()
{
    var temp = new SomeClass;
    Test(new Action(temp.SomeOddMethod));
}

private class SomeClass
{
    private void SomeOddMethod()
    {
        Debug.WriteLine("Test");
    }
}

вот, например, что такое анонимный метод на самом деле.

вещи становятся немного более волосатыми, если вы начинаете использовать локальные переменные, рассмотрим этот пример:

public void Test()
{
    int x = 10;
    Test(delegate { Debug.WriteLine("x=" + x); });
}

вот что происходит под капотом, или, по крайней мере, что-то очень близкое к нему:

public void TestSomething()
{
    var temp = new SomeClass;
    temp.x = 10;
    Test(new Action(temp.SomeOddMethod));
}

private class SomeClass
{
    public int x;

    private void SomeOddMethod()
    {
        Debug.WriteLine("x=" + x);
    }
}

компилятор создает класс, поднимает все переменные, которые требуется методу, в этот класс и перезаписывает весь доступ к локальным переменным доступ к полям анонимного типа.

имя класса и метод немного странные, давайте спросим помощью linqpad что будет:

void Main()
{
    int x = 10;
    Test(delegate { Debug.WriteLine("x=" + x); });
}

public void Test(Action action)
{
    action();
}

если я попрошу LINQPad вывести IL (промежуточный язык) этой программы, я получу следующее:

// var temp = new UserQuery+<>c__DisplayClass1();
IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
IL_0005:  stloc.0     // CS$<>8__locals2
IL_0006:  ldloc.0     // CS$<>8__locals2

// temp.x = 10;
IL_0007:  ldc.i4.s    0A 
IL_0009:  stfld       UserQuery+<>c__DisplayClass1.x

// var action = new Action(temp.<Main>b__0);
IL_000E:  ldarg.0     
IL_000F:  ldloc.0     // CS$<>8__locals2
IL_0010:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
IL_0016:  newobj      System.Action..ctor

// Test(action);
IL_001B:  call        UserQuery.Test

Test:
IL_0000:  ldarg.1     
IL_0001:  callvirt    System.Action.Invoke
IL_0006:  ret         

<>c__DisplayClass1.<Main>b__0:
IL_0000:  ldstr       "x="
IL_0005:  ldarg.0     
IL_0006:  ldfld       UserQuery+<>c__DisplayClass1.x
IL_000B:  box         System.Int32
IL_0010:  call        System.String.Concat
IL_0015:  call        System.Diagnostics.Debug.WriteLine
IL_001A:  ret         

<>c__DisplayClass1..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ret         

здесь вы можете видеть, что имя класса UserQuery+<>c__DisplayClass1 и название метода <Main>b__0. Я редактировал в коде C#, который произвел этот код, LINQPad ничего не производит но IL в приведенном выше примере.

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

так вот что такое в основном анонимный метод.

что это?

Test(() => Debug.WriteLine("Test"));

Ну, в данном случае это то же самое, это ярлык для создания анонимного метода.

вы можете написать это в двух пути:

() => { ... code here ... }
() => ... single expression here ...

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

в этом случае компилятор будет считать этот:

() => ...

так же, как это:

delegate { ... }

они все еще анонимные методы, просто () => синтаксис-это ярлык для его получения.

Итак, если это кратчайший путь к нему, почему он у нас есть?

Ну, это делает жизнь немного проще, с целью которой она была добавлена, то есть LINQ.

рассмотрим это утверждение LINQ:

var customers = from customer in db.Customers
                where customer.Name == "ACME"
                select customer.Address;

этот код переписывается следующим образом:

var customers =
    db.Customers
      .Where(customer => customer.Name == "ACME")
      .Select(customer => customer.Address");

если бы вы использовали delegate { ... } синтаксис, вам придется переписать выражения с return ... и так далее, и они будут выглядеть более напуганными. Таким образом, был добавлен синтаксис лямбда, чтобы облегчить нам жизнь программисты при написании кода, как указано выше.

так что же такое выражения?

до сих пор я не показал, как Test был определен, но давайте определим Test для приведенного выше кода:

public void Test(Action action)

этого должно хватить. В нем говорится, что "мне нужен делегат, он имеет тип Action (не принимает параметров, не возвращает значений)".

однако Microsoft также добавила другой способ определить это метод:

public void Test(Expression<Func<....>> expr)

обратите внимание, что я уронил часть там,.... часть, давайте вернемся к этому 1.

этот код, в паре с этим призывом:

Test(() => x + 10);

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

var operand1 = new VariableReferenceOperand("x");
var operand2 = new ConstantOperand(10);
var expression = new AdditionOperator(operand1, operand2);
Test(expression);

в основном компилятор будет создавать Expression<Func<...>> "объект", содержащий ссылки на переменные, константы, операторы и т. д. и передайте это дерево объектов методу.

почему?

Ну, считай db.Customers.Where(...) часть выше.

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

это цель выражения. Entity Framework, Linq2SQL или любой другой поддерживающий LINQ уровень базы данных возьмут это выражение, проанализируют его, выделят и напишут правильно отформатированный SQL для выполнения в базе данных.

это может никогда сделать, если мы все еще даем ему делегатов методов, содержащих IL. Он может сделать это только из-за пары вещи:

  1. синтаксис разрешен в лямбда-выражении, подходящем для Expression<Func<...>> ограничено (без заявлений и т. д.)
  2. лямбда-синтаксис без фигурных скобок, который говорит компилятору, что это более простая форма код

Итак, подведем итоги:

  1. анонимные методы на самом деле не все анонимны, они заканчиваются как именованный тип с именованным методом, только вам не нужно называть их вещи сами
  2. это много магии компилятора под капотом, который перемещает вещи вокруг, так что вам не нужно
  3. выражения и делегаты-это два способа взглянуть на некоторые из тех же вещей
  4. выражения предназначены для фреймворков, которые хотят знать что делает код и как, чтобы они могли использовать эти знания для оптимизации процесса (например, написание инструкции SQL)
  5. делегаты предназначены для рамок это касается только возможность вызова метода

Примечания:

  1. на .... часть для такого простого выражения для типа возвращаемого значения, можно получить из выражения. The () => ... simple expression ... только выражения, то есть то, что возвращает значение, и это не может быть несколько операторов. Таким образом, допустимым типом выражения является следующее:Expression<Func<int>>, в основном, выражение является функцией (методом), возвращающей целочисленное значение.

    обратите внимание, что "выражение, которое возвращает значение" является пределом для Expression<...> параметры или типы, а не делегатов. Это полностью юридический код, если тип параметра Test это Action:

    Test(() => Debug.WriteLine("Test"));
    

    очевидно, Debug.WriteLine("Test") ничего не возвращает, но это законно. Если метод Test требуется выражение однако, это не будет, так как выражение должно возвращать значение.


есть одно тонкое различие, о котором вы должны знать. Рассмотрим следующие запросы (используя пресловутый NorthWind).

Customers.Where(delegate(Customers c) { return c.City == "London";});
Customers.Where(c => c.City == "London");

первый использует анонимный делегат, а второй использует лямбда-выражение. Если вы оцените результаты обоих, вы увидите одно и то же. Однако, глядя на сгенерированный SQL, мы увидим совсем другую историю. Первый генерирует

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]

в то время как второй создает

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]
WHERE [t0].[City] = @p0

уведомления в первом случае предложение where не передается в базу данных. Почему так? Компилятор может определить, что лямбда-выражение является простым однострочным выражением, которое может быть сохранено как дерево выражений, тогда как анонимный делегат не является лямбда-выражением и, следовательно, не может быть обернут как Expression<Func<T>>. В результате в первом случае лучшим соответствием для метода расширения Where является тот, который расширяет IEnumerable, а не версию IQueryable, которая требует Expression<Func<T, bool>>.

на данный момент, мало пользы от анонимного делегата. Это более многословно и менее гибко. В общем, я бы рекомендовал всегда использовать синтаксис лямбда над синтаксисом анонимного делегата и подобрать синтаксическую разборчивость и синтаксическую краткость.


Если быть точным, то, что вы называете "анонимный делегат", на самом деле является анонимным методом.

Ну, как лямбды, так и анонимные методы-это просто синтаксический сахар. Компилятор будет генерировать по крайней мере "нормальный" метод для вас, хотя иногда (в случае закрытия) он будет генерировать вложенный класс с уже не анонимным методом в нем.