每次和人讲解C#的委托以及Lambda都比较费劲,其实概念是非常简单的,但不好描述。今天写了一个非常简单粗暴直观的例子,来给大家看一下Labmda、委托的用法和关系,顺便也演示了拓展方法、对象初始化器的使用。

我描述的是这样的场景:有3个屌丝,他们每个人都要做两件事,打魔兽和撸撸睡。

首先,我定义了一个屌丝类,描述一个屌丝对象:

class Diaos
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

然后,我们要创建3个屌丝,以往我们用C#来让完成这件事通常会这样写:

List<Diaos> diaoses = new List<Diaos>();
Diaos d1 = new Diaos();
d1.Id = Guid.NewGuid();
d1.Name = "Michael";
diaoses.Add(d1);
Diaos d2 = new Diaos();
d2.Id = Guid.NewGuid();
d2.Name = "Edi";
diaoses.Add(d2);
Diaos d3 = new Diaos();
d3.Id = Guid.NewGuid();
d3.Name = "Kasim";
diaoses.Add(d3);

显然这是一个简单的操作,但代码却如此复杂。如果使用var关键词和对象初始化器来做这件事就会截然不同:

var diaoes = new List<Diaos>()
{
    new Diaos() { Id = Guid.NewGuid(), Name = "Michael" },
    new Diaos() { Id = Guid.NewGuid(), Name = "Edi" },
    new Diaos() { Id = Guid.NewGuid(), Name = "Kasim" }
};

这段代码非常清晰易懂。var的意思不是没有类型或者弱类型,而是让编译器在编译时推断类型。在编程时只是节约了我们的代码量,特别是碰到名字非常长的对象时,var可以省去我们很多键入操作。对象初始化器的语法也很简单,在new一个对象的时候,后面加上一对花括号,就可以在花括号里给对象的属性赋值。

现在我们有了3个屌丝,怎么样让他们先打魔兽再撸撸睡呢?以前我们会这样写:

private static void PlayWOW(Diaos diaos)
{
    Console.WriteLine("{0} played WOW with his gay friend.", diaos.Name);
}

private static void LuLuSleep(Diaos diaos)
{
    Console.WriteLine("{0} lued one shot and went to sleep.", diaos.Name);
}
...
foreach (var item in diaoes)
{
    PlayWOW(item);
    LuLuSleep(item);
}

然而,我们认为"打魔兽"和"撸撸睡"是属于屌丝本身的操作,如果可以直接“.”出来不是更自然吗?那就改用拓展方法吧!拓展方法要放在一个静态类里,所以拓展方法自然也都是静态方法,被拓展的类型前面加this就好了,就像这样:

static class DiaosExtensions
{
    public static void PlayWOW(this Diaos diaos)
    {
        Console.WriteLine("{0} played WOW with his gay friend.", diaos.Name);
    }

    public static void LuLuSleep(this Diaos diaos)
    {
        Console.WriteLine("{0} lued one shot and went to sleep.", diaos.Name);
    }
}

现在我们就可以把那段foreach循环改成:

foreach (var item in diaoes)
{
    item.PlayWOW();
    item.LuLuSleep();
}

这段代码看上去更加自然和牛逼。如果我们要进一步装逼,我们可以把这段foreach代码也省了,改用List的ForEach()拓展方法,往里面传一个委托。听晕了?其实很简单:

diaoes.ForEach(delegate(Diaos d) { d.PlayWOW(); d.LuLuSleep(); });

这里的delegate是对被ForEach的对象(即Diaos对象)进行的一些列的操作,所以delegate的类型一定要是Diaos。一般我们会在代码的别的地方定义一个方法体(比较主流的做法),但是直接写在delegate里面也是可以的(它原本就是这样用的)。

我本人认为delegate是一种多态的表现,它可以像传参数一样,传一个方法,这个方法自然是可变的。

如果你都懒的写delegate这些字,那就用lambda表达式,lambda只是delegate的语法糖:

diaoes.ForEach(p => { p.PlayWOW(); p.LuLuSleep(); });

“p”只是一个“临时变量”,可以看作占位符,它代表lambda表达式里处理的对象,这里就是Diaos类型的对象。

如果你只想做一个操作,那么大括号也可以省略:

diaoes.ForEach(p => p.PlayWOW());

最后,整个例子的完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LambdaTest
{
    class Diaos
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }

    static class DiaosExtensions
    {
        public static void PlayWOW(this Diaos diaos)
        {
            Console.WriteLine("{0} played WOW with his gay friend.", diaos.Name);
        }

        public static void LuLuSleep(this Diaos diaos)
        {
            Console.WriteLine("{0} lued one shot and went to sleep.", diaos.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var diaoes = new List<Diaos>()
            {
                new Diaos() { Id = Guid.NewGuid(), Name = "Michael" },
                new Diaos() { Id = Guid.NewGuid(), Name = "Edi" },
                new Diaos() { Id = Guid.NewGuid(), Name = "Kasim" }
            };

            foreach (var item in diaoes)
            {
                item.PlayWOW();
                item.LuLuSleep();
            }

            //diaoes.ForEach(p => p.PlayWOW());
            diaoes.ForEach(p => { p.PlayWOW(); p.LuLuSleep(); });
            //diaoes.ForEach(delegate(Diaos d) { d.PlayWOW(); d.LuLuSleep(); });
        }
    }
}

运行结果: