【什么是委托】

  • 委托是对函数的引用,它是一个引用类型,类似c/cpp中的函数指针。但它是类型安全的。
  • 委托是一个类,定义了方法的类型,可以将方法当做另一个方法的参数传递。

委托就是一个安全的函数指针,用来执行函数方法的东西。


【如何使用委托】

在.Net框架下,委托的使用方法经历了多次改变。

最初委托的使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
public delegate string MyDelegate(string name, int age);
static void Main(string[] args)
{
MyDelegate md = new MyDelegate(Show);
Console.Write(md("Joe",20));
}
private static string Show(string name, int age)
{
return "Hello!" + name + ":" + age;
}

可以看到使用委托的方法是:

1.定义委托,格式为:

delegate 返回值 委托名(参数…)

2.定义委托可以调用的方法,这些方法的返回值和参数(类型,个数)必须与委托声明的返回值和参数一致。这些函数方法既可以是静态,也可以是非静态。而c/cpp中的函数指针只能调用静态方法。例子中使用的是名为Show的静态函数。当然我们也可以新建一个Test类,写一个非静态方法Show1,保证它的返回值和参数与委托一致即可。

3.实例化委托,委托与类不同,类实例化后产生一个对象,但委托实例化后仍是一个委托,可以叫他委托实例也可以叫委托对象。它的实例化格式与类的实例化很相近。

委托名 实例名 = new 委托名(方法名)

如果使用委托调用第2步中建立的Show1方法,则应是:

1
MyDelegate md = new MyDelegate(new Test().Show1);

4.使用委托十分简单,直接操作实例化后的委托,并传入参数就行了。

实例名(参数……)

在.Net2.0后,加入了泛型委托,.Net3.5又加入了lambda表达式。这时候委托的使用方法变的更为简单:

.Net3.5加入的两个泛型委托是Action和Func,其中Action相当于无返回值的委托而Func是有返回值的委托。
他们的声明方式为:

Func<参数1类型,参数2类型……,返回值类型>

Action<参数1类型,参数2类型……>

如果使用泛型委托,那么上面的例子则变为:

1
2
3
4
5
6
7
8
9
10
11
12
public static Func<string, int, string> myfunc;
static void Main(string[] args)
{
myfunc = new Func<string, int, string>(Show);
Console.Write(myfunc("Joe", 20));
}
private static string Show(string name, int age)
{
return "Hello!" + name + ":" + age;
}

如果使用lambda表达式,上面的例子将变得更简单:

1
2
3
4
5
6
7
8
public static Func<string, int, string> myfunc = (string name, int age) =>
{
return "Hello!" + name + ":" + age;
};
static void Main(string[] args)
{
Console.Write(myfunc("Joe", 20));
}

但是这样写过之后会有一个问题,我们把定义委托,实例化,调用方法全写在一起了,代码是简单了不少,不过委托却只能调用这一个方法了。如果重新实例化并调用,那么则会覆盖掉上一个委托实例。可以看看下面程序的运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Func<string, int, string> myfunc = (string name, int age) =>
{
return "Hello!" + name + ":" + age;
};
static void Main(string[] args)
{
Console.Write(myfunc("Joe", 20));
myfunc = new Func<string, int, string>(Show);
Console.Write(myfunc("Joe", 20));
}
private static string Show(string name, int age)
{
return "Nice to meet you!" + name + ":" + age;
}

所以说怎么使用委托,需要根据情况来选择。


【为什么要使用委托】

使用委托可以将函数方法封装在委托对象内,委托可以将一个函数作为一个参数变量在程序中传递。

根据这一个作用,我们平时可以用委托启动线程,通用类库,注册事件等等。

这里提两个重要用法:

1.多路广播委托

前面的例子中委托只包含了一个方法的调用,如果要调用多个方法,就要多次显示的重新实例化委托并调用方法。事实上通过多路广播委托,可以让委托包含多个方法。其操作方法是:通过“+=”向委托添加调用方法。通过“-=”删除委托中的方法,有点类似事件的注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public delegate void myDelegate(string str);
class Program
{
static void Main(string[] args)
{
Test t = new Test();
myDelegate md = new myDelegate(t.Func1);
md("Before += Fun2");
md += t.Func2;
md("After += Fun2 and Before -= Fun1");
md -= t.Func1;
md("After -= Fun1");
}
}
public class Test
{
public void Func1(string str)
{
Console.WriteLine("Func1:" + str);
}
public void Func2(string str)
{
Console.WriteLine("Func2:" + str);
}
}

需要注意的是,多路广播委托的返回值需要为void,因为返回值不知道返回到什么地方。

2.跨线程调用

在WPF中如果有下面的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thread thread = new Thread(new ThreadStart(TestThread));
thread.Start();
}
private void TestThread()
{
label.Content = "hello";
}
}

即在不同线程中调用控件,那么会出现下面的错误:

这时候通过委托就可以实现跨线程访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public delegate void myDelegate(string msg);
public MainWindow()
{
InitializeComponent();
Thread thread = new Thread(new ThreadStart(TestThread));
thread.Start();
}
private void TestThread()
{
myDelegate md = new myDelegate(Show);
label.Dispatcher.Invoke(md,"Hello");
}
private void Show(string msg)
{
label.Content = msg;
}