委托(delegate)
委托是一种知道如何调用方法的对象。
委托类型定义了一种委托实例(delegate instance)可以调用的方法。具体来说,它定义了方法的返回类型(return type)和参数类型(parameter type)。
举例说明:
internal class Program
{
//定义委托
private delegate int Transformer(int x);
//定义方法
private static int Square(int x) => x *= x;
private static void Main(string[] args)
{
//Transformer t = Square === Transformer t=new Transformer(Square);
Transformer t = Square;
//t(3)===t.Invoke(3);
int result = t(3);
Console.WriteLine(result);
}
}
委托和回调(callback)类似。一般指捕获类似C函数指针的结构。
用委托写插件方法
.NET 中的委托插件方法是指通过使用委托的方式,在NET 程序中动态地注入外部代码,从而实现插件的功能。委托是.NET 程序设计中的一个重要概念,可以实现方法的封装和传递。通过委托,程序可以动态地注册外部代码,从而实现插件的功能。
//定义委托
public delegate int Transformer(int x);
public class Program
{
//定义方法
private static int Square(int x) => x *= x;
private static void Main(string[] args)
{
int[] values = { 1, 2, 3 };
Util.Transform(values, Square);
foreach (var item in values)
{
Console.WriteLine(item + " ");
}
}
}
public class Util
{
public static void Transform(int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
多播委托
所有的委托实例都拥有多播能力。这意味着一个委托实例可以引用一个目标方法,也可以引用一组目标方法。委托可以使用+和+=运算符联结多个委托实例。
ps:相当于一个委托可以执行多个方法。
public delegate void ProgressReporter(int percentComplete);
public class Program
{
//定义方法
private static void Main(string[] args)
{
ProgressReporter p = WriteProgressToConsole;
p += WriteProgressToFlie;
Util.HardWork(p);
}
private static void WriteProgressToConsole(int precentComplete) => Console.WriteLine(precentComplete);
private static void WriteProgressToFlie(int percentComplete) => System.IO.File.WriteAllText("progress.txt", percentComplete.ToString());
}
public class Util
{
public static void HardWork(ProgressReporter p)
{
for (int i = 0; i < 10; i++)
{
p(i * 10);
System.Threading.Thread.Sleep(100);
}
}
}
委托是不可变的,因此调用+=和-=的实质是创建一个新的委托实例,并把它赋值给已有的变量。
如果一个多播委托拥有非void的返回类型,则调用者将从最后一个触发的方法接收返回值。前面的方法依然调用,但是返回值都会被丢弃。大部分调用多播委托的情况都会返回void类型,因此这个细小的差异就没有了。
实例目标方法和静态目标方法
将一个实例方法赋值给委托对象时,后者不但要维护方法的引用,还需要维护方法所属的实例的引用。System.Delegate类Target属性代表这个实例(如果委托引用的是一个静态方法,则该属性值为null,所以将静态方法赋值给委托时性能更优。)
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 Transformer<T>(T arg);
public class Program
{
//定义方法
private static int Square(int x) => x *= x;
private static void Main(string[] args)
{
int[] values = { 1, 2, 3 };
Util.Transformer(values, Square);
foreach (var item in values)
{
Console.WriteLine(item + " ");
}
}
}
public class Util
{
public static void Transformer<T>(T[] values, Transformer<T> t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
Func和Action委托
具有任意的返回类型和任意数目的参数。
辅助理解博客:Func和Action委托详解
1:Action用于没有返回值的方法(参数可以根据自己情况进行传递)
2:Func恰恰相反用于有返回值的方法(同样参数根据自己情况情况)
3:记住无返回就用action,有返回就用Func
委托和接口
能用委托解决的问题,都可以用接口解决。
public class Program
{
private static void Main(string[] args)
{
int[] values = { 1, 2, 3 };
Util.TransforAll(values, new Squarer());
foreach (var item in values)
{
Console.WriteLine(item + " ");
}
}
}
public interface ITransformer
{
int Transform(int x);
}
internal class Squarer : ITransformer
{
public int Transform(int x) => x * x;
}
internal class Cuber : ITransformer
{
public int Transform(int x) => x * x * x;
}
public class Util
{
public static void TransforAll(int[] values, ITransformer t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t.Transform(values[i]);
}
}
}
如果以下一个或多个条件成立,委托可能是比接口更好的选择:
- 接口内仅定义了一个方法
- 需要多播能力
- 订阅者需要多次实现接口
委托的兼容性
-
即使签名相似,委托类型也互不兼容。
-
如果委托实例指向相同的目标方法,则认为它们是等价的
如果多播委托按照相同的顺序引用相同的方法,则认为他们是等价的。
逆变和协变
协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变。
如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这是Animal向Dog方向的转变是逆变。如果一个方法要求的返回值是Animal,那么返回Dog的方法肯定是可以满足其返回值要求的,这是Dog向Animal方向的转变是协变。
由子类向父类方向转变是协变 协变用于返回值类型用out关键字
由父类向子类方向转变是逆变 逆变用于方法的参数类型用in关键字
协变逆变中的协逆是相对于继承关系的继承链方向而言的。
辅助理解博客:逆变和协变详解