Can someone explain this C# lambda syntax?
I recently came upon a static method declared as:
public class Foo
{
public static Func<HtmlHelper, PropertyViewModel, string> Render = (a, b) =>
{
a.RenderPartial(b);
return "";
};
}
Intellisense sugges开发者_开发技巧ts the usage is (for example):
string s = Foo.Render(htmlHelper, propertyViewModel);
It would seem then that the following is equivalent:
public static string Render(HtmlHelper a, PropertyViewModel b)
{
a.RenderPartial(b);
return "";
}
A) What is the name of the first style? I realize it's using lambdas; it's the =
sign that is tripping me up. I can't tokenize it ;)
B) If the two code blocks are equivalent, what is the benefit of using the former over the latter?
Ok, for clarity I'm going to write the two out again (and slightly modify the method to make it shorter)
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);
}
Firstly note that RenderDelegate
is (as S. DePouw writes), just a fancy way of using lambda syntax to write the following:
public static Func<HtmlHelper, PropertyViewModel, string> RenderDelegate =
delegate(HtmlHelper a, PropertyViewModel b)
{
return a.RenderPartial(b);
};
The difference between RenderMethod
and RenderDelegate
is that RenderMethod
is a method, wheras RenderDelegate
is a delegate, or more specifically a field of type Delegate. This means that RenderDelegate can be assigned to.
What is a Delegate?
A delegate is a type. From the MSDN documentation:
A delegate is a type that defines a method signature, and can be associated with any method with a compatible signature.
Essentially you can think of a delegate as a reference / pointer to a method, however the method that the delegate points to has to match the signature that the delegate is expecting. So for example Func<HtmlHelper, PropertyViewModel, string>
is a Delegate that expects methods with the signature string MyMethod(HtmlHelper, PropertyViewModel)
and so we are able to assign methods with that signature to that delegate like this:
RenderDelegate = RenderMethod;
Its important to note the difference between the Delegate type (note the capital D) and the delegate keyword (lower case d). In your example your using the Func<>
generic object to condense your code, however its kind of obscuring whats really going on here. Func<HtmlHelper, PropertyViewModel, string>
is a type which inherits from Delegate
, and you could use the delegate keyword to delcare an equivalent type:
delegate string MyFunction<HtmlHelper helper, PropertyViewModel string>;
static MyFunction RenderDelegate = RenderMethod;
Anonymous methods
When we assigned RenderDelegate in the first example, we didnt set RenderDelegate to an existing named method, instead we declared a new method in-line. This is known as an Anonymous Method and works because we are able to pass a code block (also declared using the delegate keyword) as a delegate parameter:
Lambda functions
Back to the original syntax - your example is using lambda syntax to delcare an anonymous delegate in a funny way. Lambda expressions are good way of declaring short inline methods which might commonly be used when dealing with lists, for example supposing we want to sort a list of HtmlHelper objects by their Name. The way of doing this is to pass a Delegate that compares two HtmlHelper objects to the lists Sort method, the sort method then uses that delegate to compare and sort the elements in the list:
static int MyComparison(HtmlHelper x, HtmlHelper y)
{
return x.Name.CompareTo(y.Name);
}
static void Main()
{
List<HtmlHelper> myList = GetList();
myList.Sort(MyComparison);
}
To avoid having loads of short methods scattered around, you can use anonymous methods to delcare the sorting method in-line. Whats also really useful about this is that the in-line method has access to variables declared in the containing scope:
int myInt = 12;
List<HtmlHelper> myList = GetList();
myList.Sort(
delegate (HtmlHelper x, HtmlHelper y)
{
return x.Name.CompareTo(y.Name) - myInt;
});
Thats still fairly quite a lot of typing however, and so the lambda sytax was born and now you can do this instead:
List<HtmlHelper> myList = GetList();
myList.Sort((x, y) => {return x.Name.CompareTo(y.Name)});
Declaring "normal" methods in this way however seems to be completely pointless to me (and makes my eyes bleed)
Delegates are incredibly useful and are (among other things) the cornerstone of the .Net event system. Some more reading to clear things up a bit:
- Delegates and events in C#
- Anonymous methods (MSDN documentation)
- Introduction to lambda expressions
A) The style is that of using delegates. The following is equivalent:
public static Func<HtmlHelper, PropertyViewModel, string> Render =
delegate(HtmlHelper a, PropertyViewModel b)
{
a.RenderPartial(b);
return "";
};
B) The benefit is that you could then treat Render as a variable within another method. In this particular instance, however, they're more or less the same benefits-wise (although the latter is a bit easier to comprehend).
For the most part they seem functionally equivalent. In fact you can pass around a normal method as a variable.
But there are subtle differences like being able to redefine the function to be something else. It is probably also different if you're using reflection, e.g. it probably isn't returned in the list of methods on the class. (Not 100% sure on the reflection part)
The following shows passing a method as a variable as well as how the 2nd way allows redefining the Func that wouldn't be possible if it were a normal method.
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;
}
}
This just got me thinking... if all methods were declared this way, you could have real first class functions in C#... Interesting....
"Render" is a function object which takes an HtmlHelper and a PropertyViewModel object as its argument and returns a string. So yes, they are equivalent.
Why someone would use the lambda instead of a static function in this case is beyond me, but I do not know the context. I would just declare a static function as you have in your second example. Perhaps they think the lambda syntax is "cooler" and can't help themselves :).
I think the biggest advantage to this syntax is that you can redefine the method without extending the class (just by setting the field to a new method).
Is it a good idea? Probably not. But I'm sure there are places that it would make sense...
"A) What is the name of the first style? I realize it's using lambdas; it's the = sign that is tripping me up. I can't tokenize it ;)"
It parses like this:
"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 == ""
Sorry about that. I couldn't resist.
In this particular example, the functions are equivalent. However, in general the lambda form is a bit more powerful because it can carry around information that the method can't. For example, if I do this;
int i = 0;
Func<int> Incrementer = () => i++;
Then I have a function (Incrementer) which I can call over and over; note how it keeps hold of the variable i
, even though it's not a parameter to the function. That behaviour -- of keeping hold of the variable declared outside the method body -- is called closing over the variable. Consequently, and in answer to (A), this type of function is called a closure.
As to (B), as someone has pointed out, you could change the Render function at any point. Let's say you decide to change how Render works. Let's say you wanted to create a timed version of render. You could do this;
Foo.Render = (a, b) =>
{
var stopWatch = System.Diagnostics.Stopwatch.StartNew();
a.RenderPartial(b);
System.Diagnostics.Debug.WriteLine("Rendered " + b.ToString() + " in " + stopWatch.ElapsedMilliseconds);
return "";
};
精彩评论