2010-01-21 3 views
10

Недавно я наткнулся на статический метод объявлен как:Может кто-нибудь объяснить этот синтаксис C# лямбда?

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) Если два кодовых блока эквивалентны, в чем преимущество использования первого над последним?

+2

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

+0

Возможный дубликат выражения [C# Lambda, почему я должен использовать это?] (Http://stackoverflow.com/questions/167343/c-sharp-lambda-expression-why-should-i-use-this), а также [ func-delegate-vs-function] (http://stackoverflow.com/questions/3113226/func-delegate-vs-function) – nawfal

ответ

2

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

Но есть тонкие отличия, такие как возможность переопределить функцию как нечто другое. Это, вероятно, также отличается, если вы используете отражение, например. он, вероятно, не возвращается в списке методов класса. (Не на 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; 
    } 
} 

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

+0

«у вас может быть»: на самом деле вы можете, потому что вы можете назначить любой метод совместимому делегату. – usr

7

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

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 (как пишет С. DePouw), просто причудливый способ с использованием синтаксиса лямбда, чтобы написать следующее:

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

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

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

Делегат - это тип. От MSDN documentation: :

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

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

RenderDelegate = RenderMethod; 

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

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

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

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

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

Назад к оригинальному синтаксису - ваш пример используя синтаксис лямбда для любознательного анонимного делегата. Лямбда-выражения - хороший способ объявить короткие встроенные методы, которые обычно можно использовать при работе со списками, например, предположим, что мы хотим отсортировать список объектов 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; 
    }); 

Thats еще довольно довольно много печатать на машинке, однако, и поэтому лямбда sytax родился и теперь вам может сделать это вместо:

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

Декларирование «нормальные» методы в этом случае, однако, как представляется, совершенно бессмысленно для меня (и делает мои глаза кровоточат)

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

+1

+1 на кровоточащих глазах – jschmier

+0

Переписан для четкости. – Justin

5

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

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

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

+0

Итак, просто чтобы уточнить, подпись 'static string Render (HtmlHelper a, PropertyViewModel b)' означает 'Foo.Render' * не может быть передана *? –

+3

Любая группа методов может быть передана методу, ожидающему делегата. Первый стиль не имеет уникального преимущества. – dtb

+2

@dtb, я бы не сказал, что у него нет уникального преимущества. объявление обычного метода делает метод readonly. Если он объявлен как статическая переменная типа Func, тогда «метод» может быть изменен во время выполнения. – Davy8

1

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

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

+0

Как указано выше, преимущество заключается в том, что получаемая функция может передаваться как переменная. (Например: для предоставления в качестве обратного вызова) – Toji

+2

И вы не думаете, что это возможно с использованием стандартного метода «простой старый»? Как вам назначить обработчики событий, то интересно? –

1

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

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

1

«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 == "" 

К сожалению об этом. Я не мог сопротивляться.

1

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

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

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

Что касается (B), как указал кто-то, вы можете изменить функцию рендеринга в любой точке. Предположим, вы решили изменить работу 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 ""; 
}; 
+0

Хороший вопрос об аспекте закрытия. Кроме того, сохранение ссылки на функцию до перезаписывания будет по-прежнему держать переменные в ее закрытии, поэтому вы все равно можете называть ее в своей перезаписанной функции! (Я думаю) –