简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)。

 定义一个委托类型  Calculator:

delegate int Calculator(int x);//定义一个委托(此委托适用于任何有着int返回类型和一个int类型参数的方法)
class Program {
    static int Double(int x) { return x * 2; }//定义一个方法
    static void Main(string[] args) {
        //Calculator c = new Calculator(Double);//创建一个委托实例,将该此方法赋值给该委托实例
        Calculator c = Double;//简写
        int result = c(2);//通过委托调用方法

        Console.Write(result);//打印
        Console.ReadKey();
    }
}   

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

 

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方法是临时的,只调用一次,使用lambda表达式即可:

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

多播委托

一个委托实例不仅可以指向一个方法,还可以指向多个方法。

“+=” 用来添加,“-=”用来移除。

调用时,按照方法被添加的顺序依次执行。

对于委托,+= 和 -= 对null是不会报错的

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

示例:

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);
            }
        }
    }
}
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属性是Null,所以将静态方法赋值给委托时性能更优。

//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)    
  }
} class X { public void InstanceProgress(int percentComplete) { // do something
  }
}

泛型委托

含有泛型参数的委托

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 委托

能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托(in表示参数,out表示返回结果)

除了ref参数和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

应用

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, Func<T,T> c) 
  {    
      for (int i = 0; i < values.Length; i++)        
      values[i] = c(values[i]);
  }
}

委托的兼容

类型兼容

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

参数类型兼容

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

返回值类型兼容

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

 

事件

当我们使用委托场景时,我们很希望有这样两个角色出现:广播者和订阅者。我们需要这两个角色来实现订阅和广播这种很常见的场景。

广播者这个角色应该有这样的功能:包括一个委托字段,通过调用委托来发出广播。而订阅者应该有这样的功能:可以通过调用 += 和 -= 来决定何时开始或停止订阅。

事件就是描述这种场景模式的一个词。事件是委托的一个子集,为了满足“广播/订阅”模式的需求而生。

声明一个事件,只需在声明一个委托对象时,加上event关键字;

事件的使用和委托完全一样,只是多了些约束;

可以用事件的地方就一定可以用委托代替;

事件保证了程序的安全性和健壮性。

事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。

如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。

public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
public class IPhone6 {
    decimal price;
    public event PriceChangedHandler PriceChanged;
    public decimal Price {
        get { return price; }
        set {
            if (price == value) return;
            decimal oldPrice = price;
            price = value;
            // 如果调用列表不为空,则触发。
            if (PriceChanged != null)
                PriceChanged(oldPrice, price);
        }
    }
}
class Program {
    static void Main() {
        IPhone6 iphone6 = new IPhone6() { Price = 5288 };
        // 订阅事件
        iphone6.PriceChanged += iphone6_PriceChanged;

        // 调整价格(事件发生)
        iphone6.Price = 3999;

        Console.ReadKey();
    }

    static void iphone6_PriceChanged(decimal oldPrice, decimal price) {
        Console.WriteLine("年终大促销,iPhone 6 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
    }
}

事件的标准模式

.NET 框架为事件编程定义了一个标准模式。

设定这个标准是为了让.NET框架和用户代码保持一致。

System.EventArgs是标准模式的核心,它是一个没有任何成员,用于传递事件参数的基类。

//定义EventArgs
public class PriceChangedEventArgs : System.EventArgs 
{
    public readonly decimal OldPrice;
    public readonly decimal NewPrice;
    public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}
public class IPhone6 {
    decimal price;
    //为事件定义委托
    //必须是 void 返回类型
    //必须有两个参数,且第一个是object类型,第二个是EventArgs类型(的子类)
    //名称必须以EventHandler结尾
    //由于考虑到每个事件都要定义自己的委托很麻烦,.NET 框架为我们预定义好一个通用委托System.EventHandler
    //public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;
    //如果不使用框架的EventHandler,我们需要自己定义一个
    //public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e);
    //如果不需要参数,可以直接使用EventHandler
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
    //事件标准模式还需要写一个受保护的虚方法来触发事件,这个方法必须以On为前缀,加上事件名(PriceChanged),还要接受一个EventArgs参数
    protected virtual void OnPriceChanged(PriceChangedEventArgs e) {
        if (PriceChanged != null) PriceChanged(this, e);
    }

    public decimal Price {
        get { return price; }
        set {
            if (price == value) return;
            decimal oldPrice = price;
            price = value;
            // 如果调用列表不为空,则触发。
            if (PriceChanged != null)
                OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
        }
    }
}
class Program {
    static void Main() {
        IPhone6 iphone6 = new IPhone6() { Price = 5288M };
        // 订阅事件
        iphone6.PriceChanged +=iphone6_PriceChanged;

        // 调整价格(事件发生)
        iphone6.Price = 3999;
        Console.ReadKey();
    }

    static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) {
        Console.WriteLine("年终大促销,iPhone 6 只卖 " + e.NewPrice + " 元, 原价 " + e.OldPrice + " 元,快来抢!");
    }
}

 

posted on 2023-02-24 11:53  费良  阅读(25)  评论(0编辑  收藏  举报