作者:陈广
日期:2018-2-11
早在C# 1.0时代,就引入了委托(delegate)类型的概念,学过C语言,拿C语言弄过高级一点东西的同学都会发现,C语言里函数指针无处不在,C语言也想面向对象,也会尝试面向对象,这些功能很多时候都是用回调函数实现的,回调函数就得用到函数指针。高级语言的出现就是为了解决C语言面向对象不是那么方便、直观的问题。C语言有,C#自然就得有,委托就相当于C语言的函数指针,委托的意义在于可以将函数作为参数进行传递。某种意义上,委托可理解为一种托管的强类型函数指针。
通常情况下,使用委托需要三步走:
先来个最简单的例子演示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
这次委托带一个整数返回值,而在创建委托实例时,我们根本不需要显式地写返回值,只需要在方法体内返回相应类型值即可。
使用了匿名函数的委托变得清爽了些,但微软觉得还有改进余地,没有最好,只有更好,所以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% 的需求:
Action
:无输入参数,无返回值Action<T1, ..., T16>
:支持1-16个输入参数,无返回值Func<T1, ..., T16, Tout>
:支持1-16个输入参数,有返回值也就是说,微软已经内置了委托声明Action
和Func
,满足了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;
改完看看能不能运行,有没有很惊喜的感觉?
现在总结一下:
return
关键字。现在我们再看委托,是不是感觉很爽!的确很爽,但是,方法看着再也不像方法了,而是Main()里的几条语句,这种情况有时很容易把我们的脑子搞混。