2013-07-17 7 views
10

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

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

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

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

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

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName) 

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

+1

Вы посмотрели на это? и, возможно, другие вопросы? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –

+0

Я полностью понимаю, что такое анонимный метод/выражение лямбда и есть использование, мой вопрос в том, что они отличаются каким-либо образом от анонимного делегаты или они одинаковые – sarepta

+2

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

ответ

24

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

Когда вы сделаете это:

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

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

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

Debug.WriteLine("Test"); 

и выделим, что в метод.

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

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

Второй вопрос легко ответить. Часть 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"); 
} 

Теперь проблема состоит в том, что комп iler не знает, что делает 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 (Intermediate Language) этой программы, Я получаю это:

// 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) 

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

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

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, который будет выполняться в базе данных.

Это могло бы никогда не делать, если бы мы все еще предоставляли его делегатам методам, содержащим ИЛ. Он может сделать это только из-за нескольких вещи:

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

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

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

Сноски:

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

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

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

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

+2

Термин «анонимный» на самом деле очень хороший для этого. Подумайте, если кто-то жертвует тонну денег на благое дело. Очевидно, что у человека есть имя, просто люди не получают деньги. В этом случае имя метода вам неизвестно, но на самом деле оно имеет имя. –

+0

Отличный дискурс, но есть тонкая разница, выходящая за рамки синтаксического сахара, когда вы пытаетесь разобраться с ними как выражением. Что произойдет, если вы попытаетесь передать анонимный делегат в метод, который принимает только выражение «>»? –

+0

Вы не можете этого сделать. Вы получаете ошибку компилятора: «Выражение анонимного метода не может быть преобразовано в дерево выражений». Это было из 'Test (delegate {return 10;});' когда метод был объявлен как 'public void Test (Expression > expr)'. Заметьте, я не говорю, что нет вещей, которые я не затронул, поэтому, если вы можете найти их, я был бы рад расширить их, но передача делегата методу, требующему выражения, идти. –

1

Чтобы быть точным, то, что вы называете «анонимным делегатом», на самом деле является анонимным методом.

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

5

Существует одна тонкая разница, о которой вы должны знать. Рассмотрим следующие запросы (используя пресловутую 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 

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

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

Смежные вопросы