# C# 委托(Delegate)

C# 中的委托类似于 C 或 C++ 中函数的指针,但它是函数指针的 “升级版”。
委托是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变,可同时指向多个函数方法。
委托特别用于实现事件和回调方法。所有的委托都派生自 System.Delegate 类。

# 委托的声明

  • 委托是一个类(类的声明可以在其他类的外面或里面)
  • 类是数据类型,所以委托也是一种数据类型
  • 声明的方式与一般的类不同
  • 委托所封装的方法必须类型兼容

声明委托

// 委托的关键字:delegate
public delegate string HelloDelegate(string message);

委托所封装的方法必须类型兼容

public delegate string HelloDelegate(string message);
                string Hello(string message) {return message}
                string Hello_1(string message) {return "abc"}

# 委托的一般使用

  • 实例:把方法当作参数传给另一个方法
    • 正确使用 1:模板方法,“借用 " 指定的外部方法来产生结果
      • 相当于 " 填空题”
      • 常位于代码中部
      • 委托有返回值
    • 正确使用 2:回调 (callback) 方法,调用指定的外部方法
      • 相当于 “流水线”
      • 常位于代码末尾
      • 委托无返回值
  • 注意:难精通 + 易使用 + 功能强大东西,一旦被滥用,后果非常严重
    • 缺点 1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
    • 缺点 2:使可读性下降、debug 的难度增加
    • 缺点 3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
    • 缺点 4:委托使用不当有可能造成内存泄漏和程序性能下降

# 委托、泛型委托 (使用回调方法)

使用委托

public delegate void HelloDelegate(string message);
private static void Main(string[] args)
{
    HelloDelegate helloDelegate = new HelloDelegate(Hello); // 创建委托实例
    //helloDelegate.Invoke ("Hello"); // 调用委托
    helloDelegate("Hello"); // 调用委托的简便写法
}
private static void Hello(string message)
{
    Console.WriteLine(message);
}

使用泛型委托

public delegate void HelloDelegate2<T>(T message);
private static void Main(string[] args)
{
    HelloDelegate2<float> helloDelegate2 = new HelloDelegate2<float>(Hello2<float>);
    helloDelegate2(12f);
}
private static void Hello2<T>(T message)
{
    Console.WriteLine(message);
}

官方版本(无返回值)

private static void Main(string[] args)
{
    Action<string> Act = new Action<string>(Hello3);
    Act("官方版本(无返回值)");
}
private static void Hello3<T>(T X)
{
    Console.WriteLine(X);
}

官方版本(有返回值)

private static void Main(string[] args)
{
    Func<string, string> fun_1 = new Func<string, string>(Hello4);
    Console.WriteLine(fun_1("官方版本(有返回值)"));
}
private static string Hello4<T>(T arg)
{
    return arg.ToString();
}

# 模板方法

private static void Main(string[] args)
{
    //lambda 运算符 => 
    FuncTest(1, 2, (a, b) => { return a == b; });
    // 等价于:
    //private static bool Temp(int a, int b) { return a == b; }
    //FuncTest(1, 2, Temp);
}
private static void FuncTest<T>(T message_0, T message_1, Func<T, T, bool> message_2)
{
    Console.WriteLine(message_0);
    if (message_2(message_0, message_1))
    {
        Console.WriteLine(message_1);
    }
}

# 委托的高级使用

  • 多播 (multicast) 委托
  • 多线程
    • 多线程不是本篇文章的重点,下面给出一些简单的介绍,详细内容请参阅其他文章。
    • 同步与异步的简介
      img
    • 同步调用与异步调用的对比
      • 每一个运行的程序是一个进程 (process)
      • 每个进程可以有一个或者多个线程 (thread)
      • 同步调用是在同一线程内
      • 异步调用的底层机理是多线程
      • 串行 = 同步 = 单线程,并行 = 异步 = 多线程
    • 隐式多线程和显式多线程
      • 直接同步调用:使用方法名
      • 间接同步调用:使用单播或多播委托的 Invoke 方法
      • 隐式异步调用:使用委托的 BeginInvoke
      • 显式异步调用:使用 Thread 或 Task
  • 应该适当地使用接口 (interface) 取代一些对委托的使用
    • Java 完全地使用接口取代了委托的功能,所以 Java 没有与 C# 中委托相对应的功能实体

# 多播委托

  • 每一个委托都是继承自 MulticastDelegate,也就是说,每一个委托都是多播委托
  • 有返回值的多播委托只返回最后一个方法的值
  • 多播委托可以用 += 和 -= 进行增加和减少操作
  • 给委托传递相同方法时,生成的委托实例也是相同的(同一个实例)
  • 给委托传递不同方法时,生成的委托实例也是不同的(不同的实例)
public class DelegatesTest
{
    private static void Main(string[] args)
    {
        MulticastDelegateTest M = new MulticastDelegateTest();
        M.Show_0();
        M.Show_1();
        M.Show_2();
    }
}
public class MulticastDelegateTest
{
    /// <summary>
    /// Action
    /// </summary>
    public void Show_0()
    {
        Action action = new Action(MethodTest_0);
        // 添加的原始写法
        //action = (Action)MulticastDelegate.Combine(action, new Action(MethodTest_1));
        // 减少的原始写法
        //action = (Action)MulticastDelegate.Remove(action, new Action(MethodTest_1));
        // 添加的简写
        action += MethodTest_1;
        action += MethodTest_1;
        // 减少的简写
        action -= MethodTest_1;
        action();
    }
    /// <summary>
    /// Func
    /// </summary>
    public void Show_1()
    {
        Func<string> func = () => { return "lambda_0"; };
        func += () => { return "lambda_1"; };
        func += () => { return "lambda_2"; };
        func -= () => { return "lambda_2"; };
        // 每个匿名方法都是不同的,所以结果是 lambda_2
        Console.WriteLine(func());
    }
    /// <summary>
    /// 找到所有的委托
    /// </summary>
    public void Show_2()
    {
        Action action = new Action(MethodTest_0);
        action += MethodTest_0;
        action += MethodTest_0;
        action += MethodTest_0;
        // 找到所有的委托
        Delegate[] delegates = action.GetInvocationList();
        foreach (Action item in delegates)
        {
            item();
        }
    }
    public void MethodTest_0()
    {
        Console.WriteLine("MethodTest_0");
    }
    public void MethodTest_1()
    {
        Console.WriteLine("MethodTest_2");
    }
}