委托详解

佐手、 提交于 2020-03-06 00:18:47

委托太常见了,能灵活运用可以使你在编程中游刃有余。简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同事也是力度更小的“接口”

 

委托的简单使用

一个委托类型定义了该类型的实例能调用一类方法,这些方法含有同样的返回类型和同样参数(类型和个数相同)。委托和接口一样,可以定义在类的外部。如下定义了一个委托类型-Calculator:

delegate int Calculator(int x);

此委托适用于任何有着int返回类型和一个int类型参数的方法,如:

static int Double(int x){ return x * 2;}

创建一个委托实例,将该此方法赋值给委托实例:

Calculator c=new Calculator(Double);

也可以简写成:

Calculator c=Double;

int resule=c(2);

下面是完整代码:

delegate int Calculator(int x);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        Calculator c = Double;
        int result = c(2);

        Console.Write(result);
        Console.ReadKey();
    }
}

用委托实现插件式编程

我们可以利用“委托是一个能把方法作为参数传递的对象”这一特点,来实现一种插件式编程。

例如,我们有一个Utility类,这个类实现一个通用方法(Calculate),用来执行任何有一个整型参数和整型返回值的方法。这样说有点抽象,下面来看一个例子:

delegate int Calculator(int x);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        int[] values = { 1,2,3,4};
        Utility.Calculate(values, Double);

        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}
class Utility {
    public static void Calculate(int[] values, Calculator c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

这个例子中的Utility是固定不变的,程序实现了整数的Double功能。我们可以把这个Double方法看做是一个插件,如果将来还要实现诸如求平方、求立方的计算,我们只需向程序中不断添加插件就可以了。

如果Double方法是临时的,只调用一次,若在整个程序中不会有第二次调用,那么我们可以在Main方法中更灵活的使用这种插件式编程,无需先定义方法,使用λ表达是即可,如:

。。。。。。

Utility.Calculate(values,x=>x*2);

。。。。。。

这样的代码在我们平时编程的过程中会经常遇到。

多播委托

所有的委托实例都有多播的功能。所谓多播,就像一群程序员在某招聘网站上填好了求职意向后,某天有个公司发布了一个和这个和这些程序员求职意向刚好匹配的工作, 然后这些求职者都被通知了-“有一份好工作开始招人了,你们可以直接申请去上班了”。

也就是说,一个委托实例不仅可以指向一个方法,还可以指向多个方法。例如:

MyDelegate d=MyMethod1;//“+=”用来添加,同理“-=”用来移除。

d+=MyMethod2;//d-=MyMethod2

调用时,按照方法被添加的顺序依次执行。注意,对于委托,+=和-=对null是不会报错的,如:

MyDelegate   d;

d+=MyMethod1;//相当于MyDelegate d=MyMethod1;

为了更好的理解多播委托在开发中的应用,我们模拟一下招聘网的职位匹配小工具来做示例。在职位匹配过程中会有一段处理时间,所以在执行匹配的时候要能看到执行的进度,而且还要把执行的进度和执行情况写到日志文件中。在处理完一个步骤时,将分别执行两个方法来显示和记录执行进度。

我们先定义一个委托(ProgressReporter),然后定义一个匹配方法(Match)来执行该委托中的所有方法。如下:

public delegate void ProgressReporter(int percentComplete);
public class Utility {
    public static void Match(ProgressReporter p) {
        if (p != null) {
            for (int i = 0; i <= 10; i++) {
                p(i * 10);
                System.Threading.Thread.Sleep(100);
            }
        }
    }
}

然后我们需要两个监视进度的方法,一个把进度写到Console,另一个把进度写到一个文件。如下:

class Program {
    static void Main(string[] args) {
        ProgressReporter p = WriteProgressToConsole;
        p += WriteProgressToFile;
        Utility.Match(p);
        Console.WriteLine("Done.");
        Console.ReadKey();
    }

    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }
    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}

运行结果:

 

看到这里,是不是发现很简单。

静态方法和实例方法对于委托的区别

当一个类的实例方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate类的Target属性指向的就是这个实例。举个例子:

class Program {
    static void Main(string[] args) {
        X x = new X();
        ProgressReporter p = x.InstanceProgress;
        p(1);
        Console.WriteLine(p.Target == x); // True
        Console.WriteLine(p.Method); // Void InstanceProgress(Int32)    }

    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }
    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}
class X {
    public void InstanceProgress(int percentComplete) {
        // do something    }
}

但对于静态方法,System.Delegate类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

泛型委托

如果你知道泛型,那么就是很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

public delegate T Calculator<T> (T arg);

我们可以把前面的例子改成泛型的例子,如下:

public delegate T Calculator<T>(T arg);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        int[] values = { 1, 2, 3, 4 };
        Utility.Calculate(values, Double);

        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}
class Utility {
    public static void Calculate<T>(T[] values, Calculator<T> c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

Func和Action委托

有了泛型委托,就有了一个能适用于任何类型和任意参数(类型和合理的个数)的通过委托,Func和Action。如下所示(下面的in表示参数,out表示返回结果):

delegate TResult Func <out TResult> ();delegate TResult Func <in T, out TResult> (T arg);delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... 一直到 T16
delegate void Action ();delegate void Action <in T> (T arg);delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... 一直到 T16

有了这样的委托,我们上面的Calculator泛型委托就可以删掉了,实例就可以更简洁了:

public static void Calculate<T>(T[] values, Func<T,T> c) {
    for (int i = 0; i < values.Length; i++)
        values[i] = c(values[i]);
}

Func和Action委托,除了ref参数和out参数,基本上能适用于任何泛型委托的场景,非常好用。

委托的兼容

1.委托的类型兼容

delegate void D1();delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1;

下面是被允许的:

D2 d2=new D2(d1);

对于具体相同的目标方法的委托是被视为相等的:

delegate void D();
...
D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2); // True

同理,对于多播委托,如果含有相同的方法和相同的顺序,也被视为相同。

2.参数类型兼容

在OOP中,任何使用父类的地方均可以用子类代替,这个OOP思想对委托的参数同样有效。如:

delegate void StringAction(string s);class Program {
    static void Main() {
        StringAction sa = new StringAction(ActOnObject);
        sa("hello");
    }
    static void ActOnObject(object o) {
        Console.WriteLine(o); // hello
    }
}

3.返回值类型兼容

道理和参数类型兼容一样:

delegate object ObjectRetriever();class Program {
    static void Main() {
        ObjectRetriever o = new ObjectRetriever(RetriveString);
        object result = o();
        Console.WriteLine(result); // hello    }
    static string RetriveString() { return "hello"; }
}

 

异步委托:

异步委托可以看我之前写的博客:https://mp.csdn.net/console/editor/html/103785655

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!