在C#中的委托关键字是 Delegate,委托类似于C/C++中函数的指针。是存有对某个方法引用的引用类型变量,可在运行时被改变。一般用于实现事件和回调方法。
注意:所有的委托都派生自 System.Delegate 类 委托分为 委托类型 和 委托实例 ,下面分别进行讲解。
delegate string DemoDelegate(int num);
string IntToString(int num)
{
return num.ToString();
}
int StringToInt(string num)
{
return int.Parse(num);
}
我们来分析一下这两个代码段。首先我们定义了一个委托 DemoDelegate ,委托所定义的返回值类型是 string 类型,参数只包含一个,参数类型是 int。因此根据委托定义得知只有方法的返回值类型是 string 且参数只有一个,并且 参数类型是 int 时,委托才能调用。所以符合条件的方法就只有 IntToString 。
当把方法赋值给委托变量的时候就创建了委托实例。同样我们用一个例子来看一下:
static void Main(string[] args)
{
DemoDelegate dd = IntToString;
string num = dd(123);
// 将输出 string 类型 "123"
Console.WriteLine(num);
}
委托实例本质上就是调用者委托委托方法调用被调用者,在这里就是 Main 方法委托 DemoDelegate 去调用 IntToString 方法。这样做的好处是调用者和被调用者的耦合度降低了。
小知识:上面的代码我们还可以这样写,这两种写法是等价的:
static void Main(string[] args)
{
DemoDelegate dd = new DemoDelegate(IntToString);
string num = dd.Invoke(123);
// 将输出 string 类型 "123"
Console.WriteLine(num);
}
委托的用途很多,我们这里来看一个例子,这个例子展示了委托其中一种的用途
public delegate int DemoDelegate(int num);
class Tool
{
public static void IntSquare(int[] intArray, DemoDelegate dd)
{
for (int i = 0; i < intArray.Length; i++)
{
intArray[i] = dd(intArray[i]);
}
}
}
class Program
{
static void Main(string[] args)
{
DemoDelegate dd = Square;
int[] intArray = {2,4,6 };
Tool.IntSquare(intArray, dd);
for (int i = 0; i < intArray.Length; i++)
{
Console.WriteLine(intArray[i]);
}
Console.Read();
}
static int Square(int num)
{
return num * num;
}
}
我们将委托提取出来,作为一个公共的,然后定义一个 Tool 类,其中定义了一个计算数组中每个值的方法,这个方法接受两个参数,一个是int类型的数组,另一个是 DemoDelegate 委托类型的参数。通过委托调用 Program 类中的 Square 方法来计算数组中每个数字的平方值。我们在 Main 方法中将 Square 方法赋值给委托变量,然后见数组和委托变量一同传入刚才我们定义的 Tool 类中的 IntSquare 方法,最后输出值为:4、16、36。这种用途叫做编写插件式方法,插件式方法就是只有在运行时才将方法赋值给委托。
前面的例子我们都是讲一个方法赋值给委托变量,这种叫单播委托。但是在大部分情况下我们需要将多个方法赋值给委托,这是我们就用到了多播委托。要把多个方法赋值给委托变量,我们需要用到 + 和 += ,方法如下:
Delegate d = method1;
d += method2;
当我们调用委托 d 的时候,就会按照赋值顺序来调用方法,即先调用 method1 再调用 method2 。我们有时候也需要移除委托中的某个方法,这时我们可以用 - 和 -= 进行操作,比如我们移除前面例子中的 method1 方法:
d -= method1;
当我们进行 + 或者 += 操作时,操作数可以是null,相当于把一个新值赋值给了委托变量,也就是说如下两种方法是等价的: 方法一:
Delegate d = null;
d += method1;
方法二:
d = method1;
同理,当进行 - 或者 -= 操作时,相当于把null值赋给了委托变量。
下面我们来看一下多播委托的例子:
public delegate int DemoDelegate(int num);
static void Main(string[] args)
{
DemoDelegate dd = null;
dd += Square;
dd += Remainder;
dd(5);
Console.Read();
}
static int Square(int num)
{
Console.WriteLine(num * num);
return num * num;
}
static int Remainder(int num)
{
Console.WriteLine(num % 2);
return num % 2;
}
在代码中我们定义了两个方法,分别是计算数值平方的 Square 和计算数值除以2的余数 Remainder 。在 Main 方法中我们利用 += 将两个方法赋值给委托变量 dd 。执行这段代码,最终输出结果为:25、1。当我们利用 - 或者 -= 来移除掉一个方法时,例如移除掉 Square ,这时就只会输出1,当我们把所有的方法都移除掉时,程序运行起来将会报空指针异常的错误。
注意:
实例方法和静态方法都是c#中经常用到的方法,我们可以将这两种方法都赋值给委托,因此就出现了实例方法稳妥和静态方法委托。它们之间的区别如下:
class Demo
{
public int NumAdd(int num)
{
return ++num;
}
public static int Num(int num)
{
return num;
}
}
public delegate int DemoDelegate(int num);
class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
DemoDelegate dd = demo.NumAdd;
dd(2);
Console.WriteLine("方法所属实例:"+dd.Target);
Console.WriteLine("调用方法:"+dd.Method);
DemoDelegate staticDd = Demo.Num;
staticDd(2);
Console.WriteLine("方法所属实例:" + staticDd.Target);
Console.WriteLine("调用方法:" + staticDd.Method);
Console.ReadLine();
}
}
运行以上代码,输出结果如下:
我们可以看到,将静态方法赋值给委托对象后打印方法所属实例为空。
在一些情况下我们不确定参数类型和返回值类型,这时我们就需要用到泛型委托类型,语法如下:
public delegate T DemoDelegate<T>(T arg);
我们具体看一下例子:
public delegate T DemoDelegate<T>(T num);
class Demo
{
public int NumAdd(int num)
{
return ++num;
}
}
class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
DemoDelegate<int> dd = demo.NumAdd;
Console.WriteLine(dd(2));
Console.ReadLine();
}
}
运行上面的代码,控制台将会输出结果 3
注意:我们可以将返回值类型或者参数类型固定,例如:
public delegate string DemoDelegate<T>(T arg);
public delegate T DemoDelegate<T>(int arg);
使用泛型委托的好处是可以写出一组委托类型,这组方法可以拥有热议类型的返回值和任意数量的参数。下一小节我们就来看一下具体怎么用。
delegate TResult Func<out TResult>();
delegate TResult Func<in T,out TResult>(T arg);
delegate TResult Func<in T1,in T0,out TResult>(T t1,T t2);
delegate void Action();
delegate void Action<in T> (T t);
delegate void Action(in T1,in T2)(T t1,T t2);
我们来看一下例子,以Func为例,Action同理
class Demo
{
public void Num<T>(T[] array, Func<T, T> func)
{
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(func(array[i]));
}
}
}
class Program
{
static void Main(string[] args)
{
Demo demo = new Demo();
int[] array = new int[] {2,4,6 };
demo.Num<int>(array, NumAdd);
Console.ReadLine();
}
static int NumAdd(int num)
{
return ++num;
}
}
从代码中可以看出,我们将 Demo 类中的 Num 方法的第二个参数类型写成了 Func<T,T>,这里的意思是委托实例的返回类型和类型参数都是T类型。我们在Main函数中通过委托,控制台输出结果是 3、5、7 。这时我们就看出了使用 Func 和 Action 的优点了,我们不需要在外部显式的定义委托,比较方便。
delegate void DD1();
delegate void DD2();
DD1 dd1=Method;
DD2 dd2=dd1;
delegate void DD1();
delegate void DD2();
DD1 dd1=Method;
DD2 dd2=dd1;