Может ли кто-нибудь объяснить этот синтаксис С# лямбда?

Недавно я натолкнулся на статический метод, объявленный как:

public class Foo
{
  public static Func<HtmlHelper, PropertyViewModel, string> Render = (a, b) =>
  {
    a.RenderPartial(b);
    return "";
  };
}

Intellisense предлагает использовать (например):

string s = Foo.Render(htmlHelper, propertyViewModel);

Казалось бы, следующее эквивалентно:

public static string Render(HtmlHelper a, PropertyViewModel b)
{
  a.RenderPartial(b);
  return "";
}

A) Как называется первый стиль? Я понимаю это, используя лямбды; это знак =, который отключает меня. Я не могу его обозначить;)

B) Если два кодовых блока эквивалентны, в чем преимущество использования первого над последним?

Ответ 1

По большей части они кажутся функционально эквивалентными. Фактически вы можете передать обычный метод как переменную.

Но есть тонкие различия, такие как возможность переопределить функцию как нечто другое. Это, вероятно, также отличается, если вы используете отражение, например. он, вероятно, не возвращается в списке методов класса. (Не на 100% уверены в части отражения)

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

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetFunc());   //Prints the ToString of a Func<int, string>
        Console.WriteLine(Test(5));     //Prints "test"
        Console.WriteLine(Test2(5));    //Prints "test"
        Test2 = i => "something " + i;
        Console.WriteLine(Test2(5));    //Prints "something 5"
        //Test = i => "something " + i; //Would cause a compile error

    }

    public static string Test(int a)
    {
        return "test";
    }

    public static Func<int, string> Test2 = i =>
    {
        return "test";
    };

    public static Func<int, string> GetFunc()
    {
        return Test;
    }
}

Это только заставило меня задуматься... если бы все методы были объявлены таким образом, у вас мог бы быть реальный функции первого класса на С#.. Интересно....

Ответ 2

Хорошо, для ясности я собираюсь снова написать два (и немного изменить метод, чтобы сделать его короче)

public static Func<HtmlHelper, PropertyViewModel, string> RenderDelegate = (a, b) =>
{
    return a.RenderPartial(b);
};

public static string RenderMethod(HtmlHelper a, PropertyViewModel b)
{
    return a.RenderPartial(b);
}

Во-первых, обратите внимание, что RenderDelegate есть (как пишет S. DePouw), просто приманный способ использования синтаксиса лямбда написать следующее:

public static Func<HtmlHelper, PropertyViewModel, string> RenderDelegate = 
delegate(HtmlHelper a, PropertyViewModel b)
{
    return a.RenderPartial(b);
};

Разница между RenderMethod и RenderDelegate заключается в том, что RenderMethod является методом, если RenderDelegate является делегатом или, более конкретно, полем типа Delegate. Это означает, что RenderDelegate может быть назначен.

Что такое делегат?

Делегат - это тип. Из документации MSDN:

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

По существу, вы можете представить делегата как ссылку/указатель на метод, однако метод, который указывает делегат, должен соответствовать сигнатуре, которую ожидает делегат. Так, например, Func<HtmlHelper, PropertyViewModel, string> является делегатом, который ожидает методы с сигнатурой string MyMethod(HtmlHelper, PropertyViewModel), и поэтому мы можем назначить методы с этой сигнатурой для этого делегата следующим образом:

RenderDelegate = RenderMethod;

Важно отметить разницу между типом Делегат (обратите внимание на капитал D) и делегат (нижний регистр d). В вашем примере вы используете общий объект Func<>, чтобы сконденсировать ваш код, однако его вид затушевывает то, что действительно происходит здесь. Func<HtmlHelper, PropertyViewModel, string> - это тип, который наследует от Delegate, и вы можете использовать ключевое слово delegate для получения эквивалентного типа:

delegate string MyFunction<HtmlHelper helper, PropertyViewModel string>;
static MyFunction RenderDelegate = RenderMethod;

Анонимные методы

Когда мы назначили RenderDelegate в первом примере, мы не установили RenderDelegate на существующий именованный метод, вместо этого мы объявили новый метод в строке. Это называется анонимным методом и работает, потому что мы можем передать блок кода (также объявленный с использованием ключевого слова delegate) в качестве параметра делегата:

Лямбда-функции

Возврат к исходному синтаксису - ваш пример использует синтаксис лямбда для любовного анонимного делегата. Лямбда-выражения - хороший способ объявить короткие встроенные методы, которые обычно можно использовать при работе со списками, например, предположим, что мы хотим отсортировать список объектов HtmlHelper по имени. Способ сделать это - передать делегат, который сравнивает два объекта HtmlHelper с методом сортировки списков, затем метод сортировки использует этот делегат для сравнения и сортировки элементов в списке:

static int MyComparison(HtmlHelper x, HtmlHelper y)
{
    return x.Name.CompareTo(y.Name);
}

static void Main()
{
    List<HtmlHelper> myList = GetList();
    myList.Sort(MyComparison);
}

Чтобы избежать множества коротких методов, разбросанных вокруг, вы можете использовать анонимные методы для обработки метода сортировки в строке. Также очень полезно, что встроенный метод имеет доступ к переменным, объявленным в области содержимого:

int myInt = 12;
List<HtmlHelper> myList = GetList();
myList.Sort(
    delegate (HtmlHelper x, HtmlHelper y)
    {
        return x.Name.CompareTo(y.Name) - myInt;
    });

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

List<HtmlHelper> myList = GetList();
myList.Sort((x, y) => {return x.Name.CompareTo(y.Name)});

Объявление "нормальных" методов таким образом, однако, кажется мне совершенно бессмысленным (и делает мои глаза кровоточащими)

Делегаты невероятно полезны и являются (помимо всего прочего) краеугольным камнем системы событий .Net. Еще немного чтения, чтобы немного разобраться:

Ответ 3

A) Стиль - это использование делегатов. Следующее эквивалентно:

public static Func<HtmlHelper, PropertyViewModel, string> Render = 
delegate(HtmlHelper a, PropertyViewModel b)
{
    a.RenderPartial(b);
    return "";
};

B) Преимущество состоит в том, что вы могли бы обрабатывать Render как переменную в рамках другого метода. В этом конкретном случае, однако, они более или менее одинаковы с точки зрения преимуществ (хотя последнее немного легче понять).

Ответ 4

"Render" - это объект функции, который в качестве аргумента принимает объект HtmlHelper и PropertyViewModel и возвращает строку. Так что да, они эквивалентны.

Почему кто-то будет использовать лямбда вместо статической функции, в этом случае находится вне меня, но я не знаю контекста. Я бы просто объявил статическую функцию, как в вашем втором примере. Возможно, они думают, что синтаксис лямбда "более холодный" и не может помочь себе:).

Ответ 5

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

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

Ответ 6

"A) Что такое имя первого стиля? Я понимаю его с помощью лямбда, это знак =, который меня отключает. Я не могу его обозначить;)"

Он разбирает так:

"public static Func<HtmlHelper, PropertyViewModel, string> Render = (a, b) => { a.RenderPartial(b); return ""; };"
class-member-declaration ::= field-declaration
field-declaration ::= field-modifiers type variable-declarators ";"

"public static"
field-modifiers ::= field-modifiers field-modifier

"public"
field-modifiers ::= field-modifier
field-modifier ::= "public"

"static"
field-modifier ::= "static"

"Func<HtmlHelper, PropertyViewModel, string>"
type ::= reference-type
reference-type ::= delegate-type
delegate-type ::= type-name
type-name ::= namespace-or-type-name
namespace-or-type-name ::= identifier type-argument-list

"Func"
identifier == "Func"

"<HtmlHelper, PropertyViewModel, string>"
type-argument-list ::= "<" type-arguments ">"

"HtmlHelper, PropertyViewModel, string"
type-arguments ::= type-arguments "," type-argument

"HtmlHelper, PropertyViewModel"
type-arguments ::= type-arguments "," type-argument

"HtmlHelper"
type-arguments ::= type-argument
type-argument ::= type
type ::= type-parameter
type-parameter ::= identifier
identifier == "HtmlHelper"

"PropertyViewModel"
type-argument ::= type
type ::= type-parameter
type-parameter ::= identifier
identifier == "PropertyViewModel"

"string"
type-argument ::= type
type ::= type-parameter
type-parameter ::= identifier
identifier == "string"

"Render = (a, b) => { a.RenderPartial(b); return ""; }"
variable-declarators ::= variable-declarator
variable-declarator ::= identifier "=" variable-initializer  (Here is the equals!)

"Render"
identifier == "Render"

"(a, b) => { a.RenderPartial(b); return ""; }"
variable-initializer ::= expression
expression ::= non-assignment-expression
non-assignment-expression ::= lambda-expression
lambda-expression ::= anonymous-function-signature "=>" anonymous-function-body

"(a, b)"
anonymous-function-signature ::= implicit-anonymous-function-signature
implicit-anonymous-function-signature ::= "(" implicit-anonymous-function-parameter-list ")"

"a, b"
implicit-anonymous-function-parameter-list ::= implicit-anonymous-function-parameter-list "," implicit-anonymous-function-parameter

"a"
implicit-anonymous-function-parameter-list ::= implicit-anonymous-function-parameter
implicit-anonymous-function-parameter == identifier
identifier == "a"

"b"
implicit-anonymous-function-parameter == identifier
identifier == "b"

"{ a.RenderPartial(b); return ""; }"
anonymous-function-body ::= block
block ::= "{" statement-list "}"

"a.RenderPartial(b); return "";"
statement-list ::= statement-list statement

"a.RenderPartial(b);"
statement-list ::= statement
statement ::= embedded-statement
embedded-statement ::= expression-statement
expression-statement ::= statement-expression ";"

"a.RenderPartial(b)"
statement-expression ::= invocation-expression
invocation-expression ::= primary-expression "(" argument-list ")"

"a.RenderPartial"
primary-expression ::= primary-no-array-creation-expression
primary-no-array-creation-expression ::= member-access
member-access ::= primary-expression "." identifier

"a"
primary-expression ::= primary-no-array-creation-expression
primary-no-array-creation-expression ::= simple-name
simple-name ::= identifier
identifier == "a"

"RenderPartial"
identifier == "RenderPartial"

"b"
argument-list ::= argument
argument ::= expression
expression ::= non-assignment-expression
non-assignment-expression ::= conditional-expression
conditional-expression ::= null-coalescing-expression
null-coalescing-expression ::= conditional-or-expresion
conditional-or-expresion ::= conditional-and-expression
conditional-and-expression ::= inclusive-or-expression
inclusive-or-expression ::= exclusive-or-expression
exclusive-or-expression ::= and-expression
and-expression ::= equality-expression 
equality-expression ::= relational-expression 
relational-expression ::= shift-expression
shift-expression ::= additive-expression 
additive-expression ::= multiplicitive-expression 
multiplicitive-expression ::= unary-expression
unary-expression ::= primary-expression
primary-expression ::= primary-no-array-creation-expression
primary-no-array-creation-expression ::= simple-name
simple-name ::= identifier
identifer == "b"

"return "";"
statement ::= embedded-statement
embedded-statement ::= jump-statement
jump-statement ::= return-statement
return-statement ::= "return" expression ";"

""""
expression ::= non-assignment-expression
non-assignment-expression ::= conditional-expression
conditional-expression ::= null-coalescing-expression
null-coalescing-expression ::= conditional-or-expresion
conditional-or-expresion ::= conditional-and-expression
conditional-and-expression ::= inclusive-or-expression
inclusive-or-expression ::= exclusive-or-expression
exclusive-or-expression ::= and-expression
and-expression ::= equality-expression 
equality-expression ::= relational-expression 
relational-expression ::= shift-expression
shift-expression ::= additive-expression 
additive-expression ::= multiplicitive-expression 
multiplicitive-expression ::= unary-expression
unary-expression ::= primary-expression
primary-expression ::= primary-no-array-creation-expression
primary-no-array-creation-expression ::= literal
literal ::= string-literal
string-literal == ""

Извините. Я не мог сопротивляться.

Ответ 7

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

int i = 0;
Func<int> Incrementer = () => i++;

Тогда у меня есть функция (Incrementer), которую я могу вызвать снова и снова; обратите внимание, как он удерживает переменную i, даже если это не параметр для функции. Это поведение - сохранение переменной, объявленной вне тела метода, называется закрытием по переменной. Следовательно, и в ответ на (A) этот тип функции называется замыканием.

Что касается (B), как указал кто-то, вы можете изменить функцию Render в любой точке. Скажем, вы решили изменить работу Render. Скажем, вы хотели создать временную версию рендера. Вы можете сделать это:

Foo.Render = (a, b) =>
{
  var stopWatch = System.Diagnostics.Stopwatch.StartNew();
  a.RenderPartial(b);
  System.Diagnostics.Debug.WriteLine("Rendered " + b.ToString() + " in " + stopWatch.ElapsedMilliseconds);
  return "";
};