首页 > 代码库 > .Net委托详解

.Net委托详解

搬运自http://wurang.me/2014/04/22/delegate.html

【什么是委托】

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

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

【如何使用委托】

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

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

 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方法,则应是:

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类型……>

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

 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表达式,上面的例子将变得更简单:

   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));
        }

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

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.多路广播委托

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

 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中如果有下面的场景:

  public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Thread thread = new Thread(new ThreadStart(TestThread));
            thread.Start();
        }

        private void TestThread()
        {
            label.Content = "hello";
        }
    }

 

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

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

  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;
        }