Lambda表达式 - 委托进化史

作者:陈广
日期:2018-2-11


委托

早在C# 1.0时代,就引入了委托(delegate)类型的概念,学过C语言,拿C语言弄过高级一点东西的同学都会发现,C语言里函数指针无处不在,C语言也想面向对象,也会尝试面向对象,这些功能很多时候都是用回调函数实现的,回调函数就得用到函数指针。高级语言的出现就是为了解决C语言面向对象不是那么方便、直观的问题。C语言有,C#自然就得有,委托就相当于C语言的函数指针,委托的意义在于可以将函数作为参数进行传递。某种意义上,委托可理解为一种托管的强类型函数指针。

通常情况下,使用委托需要三步走:

  1. 定义一个委托,包含指定的参数类型和返回值类型。
  2. 创建一个与上述委托参数类型返回值相符的方法。
  3. 创建一个委托实例。

先来个最简单的例子演示C# 1.0中的委托,先创建一个控制台项目。项目创建方法请参考ASP.NET Core 2.0入门 系列文章的第一篇文章开发环境安装

using System;

namespace lambda
{
    class Program
    {
        //创建一个委托,返回值为空,参数个数1个,类型为字符串
        delegate void TestDelegate(string s);
        //参考委托创建一个方法,返回值为空,参数个数1个,类型为字符串
        static void PrintString(string s)
        {
            Console.WriteLine(s);
        }
        static void Main(string[] args)
        {
            //创建委托实例,注意:参数“PrintString”必须和上面的方法签名相同
            TestDelegate testDel = new TestDelegate(PrintString);
            //调用委托
            testDel("Hello Lambda!");
        }
    }
}

注意,这里只是演示委托的使用,尽可能简单。实际应用中如果仅打印一个字符串,用方法就可以了,没必要绕个圈使用委托。

匿名函数

通过上例,大家可能会觉得委托用起来实在太麻烦,声明一堆东西才能使用。微软当然也意识到了这个问题,所以接下来的C# 2.0推出了匿名函数。下面我们用匿名函数修改上例:

using System;

namespace lambda
{
    class Program
    {
        //创建一个委托,返回值为空,参数个数1个,类型为字符串
        delegate void TestDelegate(string s);
        static void Main(string[] args)
        {
            //创建委托实例,注意delegate里面参数类型和个数必须符合上面委托的声明
            TestDelegate testDel = delegate(string s)
            {   //第2步需要创建的方法现在跑这里了
                Console.WriteLine(s);
            }; //注意这里要加分号
            //调用委托
            testDel("Hello Lambda!");
        }
    }
}

现在我们发现委托的编写简单一些了,不再需要单独声明方法,创建委托实例的时候直接把方法主体写在下面就OK了。注意,如果方法体很长,或需要多处创建实例,那还是需要使用老办法。匿名函数只是使单次创建实例更为方便。

看到这里大家可能要问了,如果方法需要返回值怎么办?继续改代码:

using System;

namespace lambda
{
    class Program
    {
        //创建一个委托,返回值为整数,参数个数1个,类型为字符串
        delegate int TestDelegate(string s);
        static void Main(string[] args)
        {
            //创建委托实例,注意delegate里面参数类型和个数必须符合上面委托的声明
            TestDelegate testDel = delegate(string s)
            {
                return s.Length;
            }; //注意这里要加分号
            //调用委托
            Console.WriteLine(testDel("Hello Lambda!"));
        }
    }
}

运行结果:13
这次委托带一个整数返回值,而在创建委托实例时,我们根本不需要显式地写返回值,只需要在方法体内返回相应类型值即可。

lambda表达式

使用了匿名函数的委托变得清爽了些,但微软觉得还有改进余地,没有最好,只有更好,所以C# 3.0推出的lambda表达式进一步简化了委托的使用。微软意识到,delegate关键字也是可以省略的。继续修改例子,不带参数版:

using System;

namespace lambda
{
    class Program
    {
        delegate void TestDelegate(string s);
        static void Main(string[] args)
        {
            //创建委托实例,注意括号里面参数类型和个数必须符合上面委托的声明
            TestDelegate testDel = (string s) => Console.WriteLine(s);
            testDel("Hello Lambda!");
        }
    }
}

这次改进,把delegate关键字换成了=>。并且,由于方法体只有一句代码,直接写在箭头后面完事,现在看清爽了很多啊!当然如果有多条语句,还是要写大括号的。

using System;

namespace lambda
{
    class Program
    {
        delegate void TestDelegate(string s);
        static void Main(string[] args)
        {
            TestDelegate testDel = (string s) => 
            {
                Console.WriteLine(s);
                Console.WriteLine("I am coming to you");
            };
            testDel("Hello Lambda!");
        }
    }
}

下面是带参数版:

using System;

namespace lambda
{
    class Program
    {
        delegate int TestDelegate(string s);
        static void Main(string[] args)
        {
            TestDelegate testDel = (string s) =>
            {
                return s.Length;
            };
            Console.WriteLine(testDel("Hello Lambda!"));
        }
    }
}

别急!C# 3.0可不仅仅是这些改动。现在看微软代码,根本看不到delegate关键字,跑哪去了?由于早在C# 2.0就有了泛型,到了3.0微软才意识到实际上仅通过两种泛型委托就可以满足 99% 的需求:

也就是说,微软已经内置了委托声明ActionFunc,满足了99%的情况,你不需要再自己声明委托了。

下面我们为每项写一个程序。
无参无返回值版:

using System;
namespace lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            Action testDel = () => Console.WriteLine("Hello Lambda");
            testDel();
        }
    }
}

有参无返回值版,对应之前的例子:

using System;
namespace lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<string> testDel = (s) => Console.WriteLine(s);
            testDel("Hello Lambda!");
        }
    }
}

泛型在我之前制作的视频里好象只讲了一集,不理解先把泛型学通了再来学lambda吧。Action<string> 把委托参数限制为只有一个参数,且必须为字符串。

有参有返回值版,对应之前的例子:

using System;
namespace lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<string, int> testDel = (s) =>
            {
                return s.Length;
            };
            Console.WriteLine(testDel("Hello Lambda!"));
        }
    }
}

这里需要注意:Func<> 中的最后一个参数为返回值类型,所以Func<string, int> 为带一个string类型参数,返回值为int 的委托。

别急,还没完,微软还是没有满足,还能更简化。把上有参无返回值版中的(s)改成s看看:

Action<string> testDel = s => Console.WriteLine(s);

再把有参有返回值版的return关键字去掉:

Func<string, int> testDel = s => s.Length;

改完看看能不能运行,有没有很惊喜的感觉?

现在总结一下:

现在我们再看委托,是不是感觉很爽!的确很爽,但是,方法看着再也不像方法了,而是Main()里的几条语句,这种情况有时很容易把我们的脑子搞混。