【问题标题】:Can someone explain this C# lambda syntax?有人可以解释这个 C# lambda 语法吗?
【发布时间】:2010-01-21 23:23:56
【问题描述】:

我最近遇到了一个声明为的静态方法:

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) 如果两个代码块等价的,那么使用前者比使用后者有什么好处?

【问题讨论】:

标签: c#


【解决方案1】:

好的,为了清楚起见,我将再次写出这两个(并稍微修改方法以使其更短)

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&lt;HtmlHelper, PropertyViewModel, string&gt; 是一个委托,它需要签名为 string MyMethod(HtmlHelper, PropertyViewModel) 的方法,因此我们可以将具有该签名的方法分配给该委托,如下所示:

RenderDelegate = RenderMethod;

请务必注意 Delegate 类型(注意大写 D)和 delegate 关键字(小写 d)之间的区别。在您的示例中,您使用Func&lt;&gt; 通用对象来压缩您的代码,但是它有点模糊了这里真正发生的事情。 Func&lt;HtmlHelper, PropertyViewModel, string&gt; 是继承自 Delegate 的类型,您可以使用 delegate 关键字来删除等价类型:

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

匿名方法

当我们在第一个示例中分配 RenderDelegate 时,我们没有将 RenderDelegate 设置为现有的命名方法,而是在线声明了一个新方法。这被称为匿名方法并且有效,因为我们能够将代码块(也使用委托关键字声明)作为委托参数传递:

Lambda 函数

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

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;
    });

然而,这仍然是相当多的输入,因此 lambda 语法诞生了,现在您可以这样做:

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

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

代表非常非常有用,并且(除其他外)是 .Net 事件系统的基石。更多阅读以澄清一些事情:

【讨论】:

    【解决方案2】:

    A) 风格是使用委托的风格。以下是等价的:

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

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

    【讨论】:

    • 所以澄清一下,签名static string Render(HtmlHelper a, PropertyViewModel b) 表示Foo.Render 不能被传递?
    • 任何方法组都可以传递给需要委托的方法。第一种风格没有独特的优势。
    • @dtb,我不会说它没有独特的优势。声明一个常规方法会使该方法成为只读的。如果它被声明为 Func 类型的静态变量,那么“方法”可以在运行时更改。
    • @Roatin - Render 在该签名中声明的方法 可以 被传递,因为 C# 会在您尝试时自动将其包装在委托中。
    【解决方案3】:

    在大多数情况下,它们在功能上看起来是相同的。事实上,您可以将普通方法作为变量传递。

    但是有一些细微的区别,比如能够将函数重新定义为其他东西。如果您使用反射,它可能也会有所不同,例如它可能不会在类的方法列表中返回。 (反射部分不是 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...有趣...。

    【讨论】:

    • “你可以拥有”:事实上你可以,因为你可以将任何方法分配给兼容的委托。
    【解决方案4】:

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

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

    【讨论】:

    • 如上所述,好处是生成的函数可以作为变量传递。 (例如:作为回调提供)
    • 您认为使用“普通旧”标准方法不可能吗?那么我想知道如何分配事件处理程序?
    【解决方案5】:

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

    这是个好主意吗?可能不是。但我敢肯定有些地方是有意义的......

    【讨论】:

      【解决方案6】:

      “A) 第一种样式的名称是什么?我意识到它使用的是 lambdas;是 = 符号让我感到困惑。我无法对其进行标记;)”

      解析如下:

      "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】:

        在这个特定的例子中,函数是等价的。但是,通常 lambda 形式更强大一些,因为它可以携带该方法不能携带的信息。例如,如果我这样做;

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

        然后我有一个函数(Incrementer),我可以一遍又一遍地调用它;注意它如何保持变量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 "";
        };
        

        【讨论】:

        • 关于闭包方面的要点。此外,在覆盖之前保存对函数的引用仍会将变量保留在其闭包中,因此您仍然可以在覆盖的函数中调用它! (我认为)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-15
        相关资源
        最近更新 更多