Lambda for Dummies ....任何人,任何人?我想不是

时间:2010-01-30 09:07:41

标签: c#-3.0 lambda

在我寻求理解非常奇怪的'=> '运营商,我找到了一个很好的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 个答案:

答案 0 :(得分:40)

让我们剖析您的代码示例:

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

因此,我们从名为string[]的{​​{1}}开始。我们在数组上调用filenames扩展方法,然后在结果上调用SelectMany

ToList

filenames.SelectMany( ... ).ToList(); 将委托作为参数,在这种情况下,委托必须将SelectMany类型的一个参数作为输入,并返回string(其中{{1}的类型推断)。这是lambdas进入舞台的地方:

IEnumerable<T>

这里会发生的是T数组中每个元素的,将调用委托。 filenames.SelectMany(f => Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true) ).ToList() 是输入参数,filenames右侧的任何内容都是委托引用的方法体。在这种情况下,将为数组中的filename调用f,使用=>参数将文件名传递给Assembly.LoadFrom方法。在返回的LoadFrom上,将调用f,它将返回AssemblyInstance个实例的数组。因此,编译器无法推断前面提到的GetCustomAttributes(typeof(PluginClassAttribute), true)的类型是Attribute

在返回的T上,系统会调用Assembly,并返回IEnumerable<Attribute>

现在我们有一个Cast<PluginClassAttribute>(),我们会在其上调用IEnumerable<PluginClassAttribute>IEnumerable<PluginClassAttribute>方法类似于Select,但返回类型Select的单个实例(由编译器推断)而不是SelectMany。设置完全相同;对于T中的每个元素,它将调用已定义的委托,将当前元素值传递给它:

IEnumerable<T>

同样,IEnumerable<PluginClassAttribute>是输入参数,.Select(a => a.PluginType) 是方法体。因此,对于列表中的每个a实例,它将返回a.PluginType属性的值(我将假设此属性的类型为PluginClassAttribute)。

执行摘要
如果我们将这些零碎的东西粘在一起:

PluginType

Lambdas与代表
让我们通过比较lambda到代表来完成这个。请按以下列表:

Type

假设我们要过滤掉以字母“t”开头的那些:

// 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" };

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

三分之一的选择是简单地将委托传递给非匿名方法:

Where

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

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

答案 1 :(得分:6)

因此,从可怕的定义开始 - lambda是另一种定义匿名方法的方法。有(因为我相信C#2.0)是一种构建匿名方法的方法 - 但是这种语法非常......不方便。

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

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

如果你正常使用这个方法说一个List并选择每个元素两次(连接到它自己):

string MyConcat(string str){
    return str + str;
}

...

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

这是一种非常不方便的方法,特别是如果您希望执行许多这些操作 - 通常也只使用一次这些方法。您展示的定义(参数=&gt;表达式)非常简洁,并且可以点击它。关于lambda的有趣之处在于,您不需要表达参数的类型 - ,只要它们可以从表达式中获取。 IE浏览器。在Select的情况下 - 我们知道第一个参数必须是TSource类型 - 因为方法的定义表明了这一点。更进一步 - 如果我们将方法称为foo.Select(...),那么表达式的返回值将定义TResult。

一个有趣的事情是,对于单语句lambda,不需要return关键字 - lambda将返回一个表达式求值的任何内容。但是,如果你使用一个块(包含在'{'和'}'),那么你必须像往常一样包含return关键字。

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

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

或者,使用明确的参数

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

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

答案 2 :(得分:5)

好吧,你可以看到一个lambda作为编写一个你只想使用一次的方法的快捷方法。例如,以下方法

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

相当于lambda:(n) => n + 5。除了您可以在类中的任何位置调用该方法,并且lambda仅存在于声明的范围内(您还可以在Action和Func对象中存储lambda)

lambdas可以为你做的其他事情是捕获范围,构建一个闭包。例如,如果你有这样的东西:

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

你所拥有的是一个像以前一样接受参数的函数,但是添加的数量来自变量的值。即使在定义了acc的范围之后,lambda也可以存活。

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

答案 3 :(得分:2)

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

a => a + 1

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

Lambdas非常有用的一种情况是当您使用它们来处理列表结构时。 System.Linq.Enumerable类提供了许多有用的函数,允许您使用Lambda表达式和实现IEnumerable的对象。例如Enumerable.Where可用于过滤列表:

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

输出将是“苹果,芒果,葡萄”。

尝试了解这里发生了什么:表达水果=&gt; fruit.Length&lt; 6创建一个函数,如果参数的Length属性小于6,则返回 true

Enumerable.Where遍历List并创建一个新List,其中只包含所提供函数返回true的元素。这样可以节省您编写迭代List的代码,检查每个元素的谓词并执行某些操作。

答案 4 :(得分:2)

一个简单的解释是针对那些熟悉编码但不熟悉lambdas的开发人员,这是TekPub上的这个简单视频

TekPub - Concepts: #2 Lambdas

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

答案 5 :(得分:1)

我知道这有点老了但是我来到这里试图理解所有这些lambda的东西。 当我完成所有答案和评论的时候,我有一个更好的lambda,并认为我应该添加这个简单的答案(从学习者的角度来看学习者):

不要将a => a + 1混淆为将a添加1并将结果返回给a。 (这很可能是初学者混淆的根源。 而是看起来像这样:a是函数的输入参数(未命名的函数),+ 1是函数中的语句(未命名的函数“动态”构造)。

希望这会有所帮助:)

答案 6 :(得分:0)

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

答案 7 :(得分:0)

这只是写下函数值的C#的符号。它不需要为函数指定名称,因此该值有时称为匿名函数。其他语言有其他符号,但它们总是包含参数列表和正文。

Alonzo Church为他的Lambda calculus在1930年代发明的原始符号在表达式λx.t中使用了希腊字符lambda来表示函数,因此得名。

答案 8 :(得分:0)

我理解lambda基础知识的技巧有两个方面。

首先,我建议学习函数式编程。在这方面,Haskell是一种很好的语言。格雷厄姆·赫顿(Graham Hutton)Programming in Haskell正在使用的这本书,并且得到了很多。这为Haskell提供了良好的基础,并包括对lambda的解释。

从那里我认为你应该查看Erik Meijer's lectures on Functional Programming,因为它们也为函数式编程提供了很好的介绍,也使用了Haskell,然后跨越到C#。

一旦你完成所有这些,你就应该很好地理解lambdas。

答案 9 :(得分:0)

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