является анонимным методом? Это действительно анонимно? У него есть имя? Все хорошие вопросы, так что давайте начнем с них и будем продвигаться к лямбда-выражениям по мере продвижения.
Когда вы сделаете это:
public void TestSomething()
{
Test(delegate { Debug.WriteLine("Test"); });
}
Что на самом деле происходит?
Компилятор сначала решает взять «тело» метода, который заключается в следующем:
Debug.WriteLine("Test");
и выделим, что в метод.
Два вопроса компилятор теперь должен ответить:
- Где я должен поставить метод?
- Как должна выглядеть подпись метода?
Второй вопрос легко ответить. Часть delegate {
отвечает на это. Метод не принимает никаких параметров (ничего между delegate
и {
), и с тех пор мы не заботимся о своем имени (отсюда «анонимным» часть), мы можем объявить метод как таковой:
public void SomeOddMethod()
{
Debug.WriteLine("Test");
}
Но почему он все это делает?
Давайте посмотрим, что такое делегат, например Action
.
Делегат, если мы на мгновение проигнорируем тот факт, что делегаты в.NET фактически связанный список нескольких сингл «делегатов», ссылка (указатель) на две вещи:
- экземпляра объекта
- метод этого экземпляра объекта
Таким образом, с что знание, первый кусок кода на самом деле может быть переписано следующим образом:
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, который будет выполняться в базе данных.
Это могло бы никогда не делать, если бы мы все еще предоставляли его делегатам методам, содержащим ИЛ. Он может сделать это только из-за нескольких вещи:
- Синтаксис позволил в лямбда-выражении подходящего для
Expression<Func<...>>
ограничен (нет заявлений и т.д.)
- с синтаксисом лямбды без фигурных скобок, которые сообщает компилятору, что это более простой вид кода
Итак, давайте подведем итоги:
- Анонимные методы на самом деле не все, что анонимно, они заканчиваются как именованный тип, с указанным методом, только вам не нужно называть эти вещи самостоятельно
- Это много магии компилятора под капотом, который перемещает вещи вокруг так, что вы не должны
- выражения и делегаты два способа смотреть на некоторых из тех же вещей
- выражений предназначены для рамок, хочет знать , что код делает и как, так что они может использовать эти знания для оптимизации процесса (например, для написания инструкции SQL)
- Делегаты предназначены для рамки, которые только обеспокоены возможности вызвать метод
Сноски:
....
часть для такого простого выражения предназначена для типа возвращаемого значения вы получаете из выражения. () => ... simple expression ...
допускает только выражения, то есть что-то, возвращающее значение, и это не может быть несколько операторов.Таким образом, допустимым типом выражения является следующее: Expression<Func<int>>
, в основном выражение представляет собой функцию (метод), возвращающую целочисленное значение.
Обратите внимание, что выражение, возвращающее значение, является пределом для параметров или типов Expression<...>
, но не для делегатов. Это вполне легальный код, если параметр тип Test
является Action
:
Test(() => Debug.WriteLine("Test"));
Очевидно, Debug.WriteLine("Test")
ничего не вернуть, но это законно. Если для метода Test
требуется выражение , это не так, поскольку выражение должно возвращать значение.
Вы посмотрели на это? и, возможно, другие вопросы? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –
Я полностью понимаю, что такое анонимный метод/выражение лямбда и есть использование, мой вопрос в том, что они отличаются каким-либо образом от анонимного делегаты или они одинаковые – sarepta
Ну, я просто написал текст с текстом, так что, надеюсь, вы найдете там свой ответ, если не сообщите мне :) –