代码之家  ›  专栏  ›  技术社区  ›  IAbstract

lambda代表假人…有人吗,有人吗?我认为不是

  •  36
  • IAbstract  · 技术社区  · 15 年前

    在我试图理解非常奇怪的“=>”运算符时,我发现 place to start 作者非常简洁明了:

    parameters => expression
    

    是否有人有任何关于理解lambda的基础知识的提示,以便更容易“解译”更复杂的lambda语句?

    例如:如果给我的东西 answer I received here )以下内容:

    filenames.SelectMany(f => 
            Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
            .Cast<PluginClassAttribute>()
            .Select(a => a.PluginType)
    ).ToList();
    

    我怎样才能把这个分解成更简单的部分呢?


    更新:想要展示我的第一个lambda表达式。别嘲笑我,但我没有模仿别人的例子就这样做了……这是第一次:

    public ModuleData[] GetStartModules( )
    { return modules.FindAll(start => start.IsBatch == true).ToArray(); }
    
    10 回复  |  直到 7 年前
        1
  •  39
  •   Fredrik Mörk    15 年前

    让我们分析一下您的代码示例:

    filenames.SelectMany(f => 
            Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
            .Cast<PluginClassAttribute>()
            .Select(a => a.PluginType)
    ).ToList();
    

    所以,我们从 string[] 打电话 filenames . 我们调用 SelectMany 数组上的扩展方法,然后调用 ToList 结果:

    filenames.SelectMany(
       ...
    ).ToList();
    

    选择许多 接受委托作为参数,在这种情况下,委托必须接受一个类型的参数 string 作为输入,并返回 IEnumerable<T> (其中 T 推断)。这就是兰姆达斯进入舞台的地方:

    filenames.SelectMany(f => 
            Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
    ).ToList()
    

    这里会发生什么事 对于每个 中的元素 文件名 数组,将调用委托。 f 是输入参数,以及 => 是委托引用的方法体。在这种情况下, Assembly.LoadFrom 将为数组中的文件名调用,并将其文件名传递到 LoadFrom 方法使用 F 争论。上 AssemblyInstance 这是回报, GetCustomAttributes(typeof(PluginClassAttribute), true) 将调用,它返回 Attribute 实例。因此编译器无法推断 T 前面提到的是 Assembly .

    IEnumerable<Attribute> 这是回报, Cast<PluginClassAttribute>() 将被调用,返回 IEnumerable<PluginClassAttribute> .

    所以现在我们有了 IEnumerable<插件包含属性> ,我们调用 Select 关于它。这个 选择 方法类似于 选择许多 ,但返回类型为的单个实例 T (由编译器推断)而不是 IEnumerable<t> .设置是相同的;对于 IEnumerable<插件包含属性> 它将调用定义的委托,并将当前元素值传递给它:

    .Select(a => a.PluginType)
    

    再一次, a 是输入参数, a.PluginType 是方法体。所以,对于每一个 PluginClassAttribute 实例在列表中,它将返回 PluginType 属性(我假设此属性属于 Type )

    执行摘要
    如果我们把这些碎片粘在一起:

    // process all strings in the filenames array
    filenames.SelectMany(f => 
            // get all Attributes of the type PluginClassAttribute from the assembly
            // with the given file name
            Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
            // cast the returned instances to PluginClassAttribute
            .Cast<PluginClassAttribute>()
            // return the PluginType property from each PluginClassAttribute instance
            .Select(a => a.PluginType)
    ).ToList();
    

    兰姆达斯与代表
    让我们通过将lambda与代表进行比较来完成这一点。从以下列表中选择:

    List<string> strings = new List<string> { "one", "two", "three" };
    

    假设我们想过滤掉以字母“T”开头的内容:

    var result = strings.Where(s => s.StartsWith("t"));
    

    这是最常见的方法;使用lambda表达式设置它。但还有其他选择:

    Func<string,bool> func = delegate(string s) { return s.StartsWith("t");};
    result = strings.Where(func);
    

    这本质上是相同的:首先我们创建一个类型的委托 Func<string, bool> (这意味着它需要 一串 作为输入参数,并返回 bool )然后我们将该委托作为参数传递给 Where 方法。这就是编译器在第一个示例的幕后为我们所做的( strings.Where(s => s.StartsWith("t")); )

    第三种选择是简单地将委托传递给非匿名方法:

    private bool StringsStartingWithT(string s)
    {
        return s.StartsWith("t");
    }
    
    // somewhere else in the code:
    result = strings.Where(StringsStartingWithT);
    

    因此,在我们这里看到的情况下,lambda表达式是定义委托的一种非常紧凑的方法,通常是指匿名方法。

    如果你的能量一直在这里读,那么,谢谢你的时间:)

        2
  •  6
  •   kastermester    15 年前

    所以,从可怕的定义开始——lambda是定义匿名方法的另一种方法。自从C 2.0以来,我相信它是一种构造匿名方法的方法-然而,语法非常…不适宜的

    那么什么是匿名方法呢?它是一种内联定义方法的方法,没有名称,因此是匿名的。如果您有一个接受委托的方法,这很有用,因为您可以将此lambda表达式/匿名方法作为参数传递,前提是类型匹配。以IEnumerable.Select为例,定义如下:

    IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
    

    如果您通常在say a list上使用此方法,并选择每个元素两次(连接到其自身):

    string MyConcat(string str){
        return str + str;
    }
    
    ...
    
    void myMethod(){
        IEnumerable<string> result = someIEnumerable.Select(MyConcat);
    }
    

    这是一种非常不明智的方法,尤其是如果您希望执行其中的许多操作——通常这些方法您只使用一次。您所显示的定义(参数=>表达式)非常一致,并且非常适合。关于lambda,有趣的是您不需要表示参数的类型- 只要它们能从表达式中推断出来 . 也就是说,在Select的情况下-我们 知道 第一个参数必须是tsource类型-因为方法的定义是这样规定的。更进一步-如果我们像foo.select(…)那样调用该方法,那么表达式的返回值将定义tresult。

    一个有趣的事情是,对于一个lambda的语句,不需要返回关键字-lambda将返回一个表达式计算得出的值。但是,如果使用块(包装在“”和“”中),则必须像往常一样包含返回关键字。

    如果愿意,定义参数类型仍然是100%合法的。有了这些新知识,让我们尝试重写前面的示例:

    void myMethod(){
        IEnumerable<string> result = someIEnumerable.Select(s => s + s);
    }
    

    或者,有明确的参数说明

    void myMethod(){
        IEnumerable<string> result = someIEnumerable.Select((string s) => s + s);
    }
    

    C中lambda的另一个有趣的特性是它们用于构造 expression trees . 不过,这可能不是“初学者”的资料,但简而言之,表达式树包含了关于lambda的所有元数据,而不是可执行代码。

        3
  •  5
  •   Mario F    15 年前

    好吧,您可以将lambda视为编写只想使用一次的方法的快速方法。例如,以下方法

    private int sum5(int n)
    {
        return n + 5;
    }
    

    等于lambda: (n) => n + 5 . 除了可以在类中的任何地方调用该方法,并且lambda只在其声明的范围内(您还可以在action和func对象中存储lambda)

    lambda可以为您做的另一件事是捕获范围,构建一个闭包。例如,如果您有类似的内容:

    ...methodbody
    int acc = 5;
    Func<int> addAcc = (n) => n + acc;
    

    您所拥有的是一个函数,它像以前一样接受一个参数,但是添加的量是从变量的值中获取的。lambda甚至可以在定义了acc的作用域完成后仍然存在。

    你可以用lambda构建非常整洁的东西,但是你必须小心,因为有时你会因为这样的技巧而失去可读性。

        4
  •  2
  •   VoidPointer    15 年前

    正如其他人所说,lambda表达式是一个函数的符号。它 绑定 表达式右侧的自由变量到左侧的参数。

    a => a + 1
    

    创建一个函数,将表达式(a+1)中的自由变量a绑定到函数的第一个参数,并返回该函数。

    lambda非常有用的一个例子是当您使用它们来处理列表结构时。System.Linq.Enumerable类提供了许多有用的函数,允许您使用lambda表达式和实现IEnumerable的对象。例如,Enumerable。可用于筛选列表的位置:

    List<string> fruits = new List<string> { 
            "apple", "passionfruit", "banana", "mango", 
            "orange", "blueberry", "grape", "strawberry" };
    
    IEnumerable<string> shortFruits = fruits.Where(fruit => fruit.Length < 6);
    
    foreach (string fruit in shortFruits) {
        Console.WriteLine(fruit);
    }
    

    产量将是“苹果,芒果,葡萄”。

    尝试理解这里发生了什么:表达式fruit=>fruit.length<6创建了一个返回 如果参数的长度属性小于6。

    可枚举。其中循环列表并创建一个新列表,该列表只包含所提供函数返回true的元素。这样可以节省您编写代码来遍历列表,检查每个元素的谓词并执行某些操作。

        5
  •  2
  •   TJB    15 年前

    一个很好的简单解释是,对于那些对编码很严格但不喜欢lambda的开发人员来说,这是一个关于tekpub的简单视频。

    TekPub - Concepts: #2 Lambdas

    很明显你在这里有很多反馈,但这是另一个很好的来源和一个简单的解释。

        6
  •  1
  •   sirbombay    12 年前

    我知道这有点老,但我来这里是想弄明白所有这些lambda的东西。 当我把所有的答案和评论都倒过来的时候,我对lambda有了更好的理解,我认为我应该添加这个简单的答案(从学习者的角度对学习者而言):

    不要混淆 a => a + 1 也就是说,将1添加到a并将结果返回到a。(这很可能是初学者混淆的原因)。 相反,请这样看:a是函数(未命名函数)的输入参数,而+1是函数中的语句(未命名函数是“动态”构造的)。

    希望这有帮助:)

        7
  •  0
  •   Tor Valamo    15 年前

    Lambda calculus 在许多编程语言中都很常见。在某些语言中,它们也被称为匿名函数。虽然不同的语言对于lambda有不同的语法,但是原理是相同的,并且它们的各个部分通常是相同的。

    也许最著名的是javascript的匿名函数。

    lol = function() {laugh()}
    # is equivalent to
    function lol() {laugh()}
    

    有什么区别?好吧,有时候你不想经历创建一个函数的麻烦,只是在某个地方传递一次,然后再也不会。

    window.onload = function() {laugh()}
    # is way easier than
    function lol() {laugh()}
    window.onload = lol
    

    你可以看到 the wikipedia article 或直接跳到 Lambda in programming 在同一篇文章中。

        8
  •  0
  •   starblue    15 年前

    这只是C的符号,用来写下一个函数值。它不需要给函数命名,因此有时将此值称为 匿名函数 . 其他语言有其他符号,但它们始终包含参数列表和主体。

    发明的原始符号 Alonzo Church 为了他的 Lambda calculus 在20世纪30年代,表达式中使用了希腊字符lambda λx.t 表示一个函数,因此是名称。

        9
  •  0
  •   Matt Ellen Bipin Vayalu    15 年前

    我的小贴士是理解兰伯斯的基础知识的两个方面。

    首先,我建议学习函数式编程。在这方面,哈斯克尔是一种很好的语言。我正在使用的一本书 Programming in Haskell 格雷厄姆·赫顿。这为哈斯克尔的研究打下了良好的基础,并包括了兰姆达斯的解释。

    从那里我想你应该看到 Erik Meijer's lectures on Functional Programming 因为它们也对函数式编程进行了很好的介绍,同时也使用了haskell,并将其转换为C。

    一旦你接受了所有这些,你就应该很好地理解兰伯斯。

        10
  •  0
  •   herzmeister    15 年前

    codeproject最近有一篇很好的介绍性文章: C# Delegates, Anonymous Methods, and Lambda Expressions – O My!