委托
什么是委托
委托是一个具有调用指定对象的指定方法的能力的对象。它是一种引用方法,当为委托分配了方法后,委托将与该方法具有完全相同的行为。
我们把为委托分配方法的过程叫做绑定。委托绑定时,指定的方法与委托的签名必须一到致。什么是委托的签名呢? 签名即是指的参数与返回类型,也就是说委托具有什么类型的参数,有几个参数,以及返回类型如何,那么与它进行绑定的方法就一定要具有相同类型和个数的参数与返回值。
下面我们来看一个关于委托的例子
using System;
usingSystem.Collections.Generic;
using System.Linq;
using System.Text;
//
usingSystem.Windows.Forms;
namespaceDelegateDemo
{
internal delegate void Feedback(intvalue);
class Program
{
static void Main(string[]args)
{
StaticDelegateDemo();
InstanceDelegateDemo();
}
privatestatic voidStaticDelegateDemo()
{
Console.WriteLine("------------------Static delegatedemo------------------------");
Counter(1,3,null);
Counter(1,3, new Feedback(FeedBackToConsole));
}
privatestatic voidInstanceDelegateDemo()
{
Console.WriteLine("------------------Instance delegatedemo------------------------");
Counter(1,3,null);
Programp = new Program();
Counter(1,3, new Feedback(p.FeedBackToMsgBox));
}
privatestatic voidCounter(int from, intto, Feedback fb) {
for(int i = from; i <= to; i++)
{
if(fb!=null)
{
fb(i);
}
}
}
privatestatic voidFeedBackToConsole(int value)
{
Console.WriteLine("Item:"+value);
}
privatevoid FeedBackToMsgBox(intvalue)
{
MessageBox.Show("Item:"+value);
}
}
}
下面我们看看运行效果是怎么样的
输出:
---------------------------------Static delegate demo---------------------------------
Item:1
Item:2
Item:3
---------------------------------Instance delegate demo------------------------------------
(弹出窗口,分别显示Item:1\Item:2\Item:3)注:此处是WinForm窗体内容
在这个例子中我们定义了一个委托internal delegate void Feedback(int value); 这个委托有一个int类型的参数,没有返回值。这些就是委托的签名。那么如果有方法想要与这个委托进行绑定,那这个方法也必须是有一个int类型的参数,也没有返回值。所以我们可以看到方法FeedBackToConsole 和方法FeedBackToMsgBox都只有一个参数(int value)且返回类型都为void。
委托绑定
那么,他们之间又是如何进行绑定的呢?在Main函数中,我们执行了两个方法,分别是StaticDelegateDemo() 和InstanceDelegateDemo()。这两个方法都有一个共性,就是都执行了Counter()方法。但结果却完全不一样。StaticDelegateDemo()方法在控制台中输出了结果,而InstanceDelegateDemo()方法却在窗口中输出了结果。为什么会有这样的差别呢?原因StaticDelegateDemo()方法中执行Counter()方法时传入了一个委托的实例new Feedback(FeedBackToConsole),而这个实例的实现则是在控制台输出内容。同理,InstanceDelegateDemo()方法中的Counter()传入的委托实例new Feedback(p.FeedBackToMsgBox)却是在窗口中显示。这就是差别的原因。
到这里大家可能会发现,FeedBackToConsole和FeedBackToMsgBox都是一个方法呀,为什么可以做为参数传递呢?平时这样做都是会报错的呀? 没错,这确实是方法,而且确实做为了方法参数进行传递。这就是委托,只有委托和事件才能把方法当成参数传递。为什么会有这样 的设计呢?我们再次看看上面的例子。对于方法Counter而言,我能确实,我要执行一个方法这个方法的形式是只有一个int类型的参数,并且没有返回值,但是这个方法内部具体如何操作,有什么样的细节操作,甚至是什么名称,我们都不能确定。而具体的执行内容可能是由客户自己来编写代码实现。在这种情况下我们就需要使用到委托。因为委托可以把方法当参数传递,这样,我在定义好了Counter后,在具体需要调用Counter的时候我再来决定在执行Counter时,到底是哪个只有一个int参数,具没有返回值的方法应该被执行。这就是什么是委托,以及委托在什么时候应该被使用到。
一个委托对象可以绑定多个实现方法。一旦相关方法被调用,所用绑定的方法都会被执行。多个实现方法的绑定,我们可以称之为委托链。下面我们来看一下,如果我想在StaticDelegateDemo()方法中,只调用一次Counter方法就分别执行在控制台和窗口上显示结果的具体实现。
private static voidStaticDelegateDemo()
{
Console.WriteLine("------------------Static delegatedemo------------------------");
Feedbackfed1 = new Feedback(FeedBackToConsole);
Programp = new Program();
fed1 += new Feedback(p.FeedBackToMsgBox);
//Counter(1,3, new Feedback(FeedBackToConsole));
Counter(1, 3, fed1);
}
现在大家可以看到我声明了一个委托实例fed1并且指定其执行的方法是FeedBackToConsole。然后,我又使用fed1 += new Feedback(p.FeedBackToMsgBox);的方法把FeedBackToMsgBox也增加到委托链上去了。其执行的效果和上面示例结果一样。通过上面我们可以看到委托可以链在一起,一次执行多个方法。当然,除了加入链以外,我们也可以使用如下方式把某一个委托从链中移除。
fed1 -= new Feedback(p.FeedBackToMsgBox);
除了使用+=和-= 来把委托实例增加或者移除委托链之外,我们也可以通过调用Delegate的静态方法Combine和Remove方法来实现同样的操作效果。
对于委托的实例化在不同的版本中不太一样。在C#1.0中,我们通过使用new关键字来实现,在上面例子中我们就是采用的这一办法。
Feedback fed1 = new Feedback(FeedBackToConsole);
在c#2.0中我们可以省略掉new 这个关键字,只要签名匹配,就可以自动识别。
Feedback fed1 = FeedBackToConsole;、
在c#3.0中,我们甚至可以使用lambda表达式来进行匿名委托的实现,如:
Counter(1,3, e => { Console.WriteLine("Item:" + e); });
对于比较简单的处理过程,我们可以如上直接使用匿名委托来实现其处理过程。上面这段代码会在我们调用Counter方法时打印出所有的数字。其输出结果与FeedBackToConsole方法完全一样。
泛型委托
除了可以使用上面的方式对委托进行操作外,委托还支持泛型。我们可以声明泛型委托像下面代码一样。
public delegate void Del﹤T﹥(T item);
我们前面说过,委托把方法的执行内部交给了使用委托的方法去实现,但委托必须定义好签名,即,有几个参数,每个参数是怎么样的,有什么样的返回值等。而泛型委托则更进了一肯,它甚至把参数类型的决定权都交由调用的方法来决定。下面我们来看一个例子:
namespaceDelegateDemo
{
internal delegate void Feedback<T>(T value);
class Program
{
static voidMain(string[] args)
{
StaticDelegateDemo();
Console.ReadKey();
}
privatestatic voidStaticDelegateDemo()
{
Console.WriteLine("------------------Static delegatedemo------------------------");
Feedback<int> fed1 = new Feedback<int>(FeedBackToConsole);
Programp = new Program();
fed1 += newFeedback<int>(p.FeedBackToMsgBox);
Counter(1, 3, fed1);
}
privatestatic voidInstanceDelegateDemo()
{
Console.WriteLine("------------------Instance delegatedemo------------------------");
Counter(1,3,null);
Programp = new Program();
}
privatestatic voidCounter(int from, intto, Feedback<int>fb) {
for(int i = from; i <= to; i++)
{
if(fb!=null)
{
fb(i);
}
}
}
private static voidFeedBackToConsole<T>(T value)
{
Console.WriteLine("Item:"+value);
}
privatevoid FeedBackToMsgBox<T>(T value)
{
MessageBox.Show("Item:"+value);
}
}
上面的代码是通过修改开篇的示例得到的,在这个示例中,我们使用到了泛型委托。internal delegatevoid Feedback<T>(Tvalue);
我们不能确实这个委托的参数类型,这需要我们在使用的时候确定。所以在Counter方法中,我们指定了参数类型。这时,如果我们也有一个Counter2方法,其目的是打印出字符串中的每个字符,那我们就可以这样写:
private static void Counter2(string str, Feedback<char> fb)
{
for(int i = 0; i < str.Length; i++)
{
if(fb != null)
{
fb(str[i]);
}
}
}
private static voidStaticDelegateDemo()
{
Console.WriteLine("------------------Static delegatedemo------------------------");
Feedback<char> fed2=FeedBackToConsole<char>;
Counter2("ss", fed2);
}
这样,我们就可以花很少的精力实现对字符的打印了。而打印的具体内容是由Counter2方法的编写者决定的。
为了使用方便,微软还为我们提供了一些已经定义好的委托如
System.Action<T>:接受一个类型为T的参数,且返回值为void, 其中参数T的个数最多可以有16个。
System.Predication<T>:接受一个类型为T的参数,且返回值为Bool,其中参数T的个数只能有一个。
System.Func<T,R>: 接受类型为T的参数,且返回值为R,其中参数T最多个数为16个,R最多1个,如果只有一个参数时效果等同于System.Func<R>,即没有传入参数,返回值为R的委托。
下面我们看一个Func<T, R>的例子:
using System;
public class Anonymous
{
public static void Main()
{
Func<string, string> convert =delegate(string s)
{ return s.ToUpper();};
string name = "Dakota";
Console.WriteLine(convert(name));
}
}
在这个例子中,我们实例了一个convert它的委托原型则是Func<T , R>然后使用匿名方法实现委托的具体处理,即把字符转换为大写前返回字符串。这个例子等同于下而的实现
using System;
delegate string ConvertMethod(string inString);
public class DelegateExample
{
public static void Main()
{
// Instantiate delegate to reference UppercaseString method
ConvertMethod convertMeth = UppercaseString;
string name = "Dakota";
// Use delegate instance to call UppercaseString method
Console.WriteLine(convertMeth(name));
}
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
}
所以可以看到,Action, Predicate, Func都是微软预先帮我们声明好的泛型委托,我们直接使用即可。省去了对委托进行定义的过程。
如果我们要对之前我们做的示例进行修改,以使用Action的话我们可以这样来改。
1、删除委托internal delegate void Feedback<T>(Tvalue);
2、把方法private static void Counter(intfrom, int to, Feedback<int> fb) 改为
private static void Counter(int from, int to, Action< int> fb)
3、在调用Couter的方法中以如下方式调用
Action< int > fed1 = FeedBackToConsole;
Counter(1,3, fed1);
4、运行即可发现效果完全一致,但省略了我们去定义委托的过程