有人可以解释这个C#lambda语法吗?

时间:2010-01-21 23:23:57

标签: 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)第一种风格的名称是什么?我意识到它正在使用lambdas;它是绊倒我的=标志。我无法将其标记化;)

B)如果两个代码块等价的,使用前者比后者有什么好处?

7 个答案:

答案 0 :(得分:7)

好的,为了清楚起见,我将再次写出两个(稍微修改方法以缩短它)

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所写),只是一种使用lambda语法编写以下内容的奇特方式:

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

RenderMethodRenderDelegate之间的区别在于RenderMethod是一种方法,其中RenderDelegate是委托,或者更具体地说是委托类型的字段。这意味着可以将RenderDelegate分配给。

什么是代表?

委托是一种类型。来自MSDN documentation

  

委托是一种定义方法签名的类型,可以与任何具有兼容签名的方法相关联。

基本上,您可以将委托视为方法的引用/指针,但委托指向的方法必须与委托所期望的签名相匹配。因此,例如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关键字声明)作为委托参数:

Lambda函数

回到原始语法 - 您的示例使用lambda语法以一种有趣的方式对一个匿名委托进行delcare。 Lambda表达式是声明在处理列表时通常可以使用的简短内联方法的好方法,例如假设我们想要按名称对HtmlHelper对象列表进行排序。这样做的方法是传递一个将两个HtmlHelper对象与列表Sort方法进行比较的Delegate,然后sort方法使用该委托来比较和排序列表中的元素:

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

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

为了避免散布各种短方法,您可以使用匿名方法对内联排序方法进行delcare。对此更有用的是内联方法可以访问在包含范围中声明的变量:

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

然而,这仍然相当多的打字,所以lambda sytax诞生了,现在你可以这样做:

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

以这种方式宣布“正常”方法然而对我来说似乎完全没有意义(并让我的眼睛流血)

代表令人难以置信地有用,并且(除其他外)是.Net事件系统的基石。还有一些阅读清理了一点:

答案 1 :(得分:5)

A)样式是使用委托。以下是等效的:

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

B)好处是你可以将Render视为另一种方法中的变量。然而,在这个特定的例子中,它们或多或少具有相同的好处(尽管后者更易于理解)。

答案 2 :(得分: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;
    }
}

这只是让我思考...如果所有方法都以这种方式声明,你可以在C#中拥有真正的first class functions ......有趣......

答案 3 :(得分:1)

“Render”是一个函数对象,它以HtmlHelper和PropertyViewModel对象作为参数并返回一个字符串。所以是的,它们是等价的。

为什么有人会在这种情况下使用lambda而不是静态函数超出我的范围,但我不知道上下文。我会在你的第二个例子中声明一个静态函数。也许他们认为lambda语法“更酷”并且无法自助:)。

答案 4 :(得分:1)

我认为这种语法的最大优点是你可以在不扩展类的情况下重新定义方法(只需将字段设置为新方法)。

这是个好主意吗?可能不是。但我确信有些地方会有意义......

答案 5 :(得分:1)

“A)第一种风格的名称是什么?我意识到它正在使用lambdas;它是sign使我失望的符号。我无法将其标记化;)”

它解析如下:

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

很抱歉。我无法抗拒。

答案 6 :(得分:1)

在这个特定的例子中,函数是等价的。但是,一般来说,lambda形式更强大,因为它可以携带该方法无法获得的信息。例如,如果我这样做;

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

然后我有一个功能(增量器),我可以一遍又一遍地调用;注意它如何保持变量i,即使它不是函数的参数。这种行为 - 保持在方法体外声明的变量 - 称为关闭变量。因此,在回答(A)时,这种类型的函数称为闭包。

至于(B),正如有人指出的那样,您可以随时更改渲染功能。假设您决定更改渲染的工作方式。假设你想要创建一个定时版本的渲染。你可以这样做;

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