C#委托的学习笔记
委托
感谢微软MVP杨旭老师的精彩讲解
真会C#? - 第4章 委托、事件、Lambda表达式(完结)_哔哩哔哩_bilibili
之所以你没有使用委托是因为你还不够了解它.._哔哩哔哩_bilibili
Delegates委托
委托类型定义了委托实例可以调用的某类方法(规定了调用的方法的返回类型和参数)
委托类型可以在命名空间声明
基本声明与调用
***注意“委托类型”和“委托实例”***
public delegate void Help();//定义一个返回值为空,委托类型为Help的 无参委托类型
public delegate int Transformer(int x);//定义一个返回值为int,委托类型为Transformer,参数为int型的带参委委托类型
public static int Square(int i) => i * i;//声明一个方法Square
Help h;//声明一个委托实例h
Transformer t = Square;//创建了委托实例t(委托对象t)
int answer = t(3);//调用 answer=9 完整调用写法:int answer = t.Invoke(3);
编写插件式的写法
class Util
{
//注意方法参数
public static void Transform(int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
static int Square(int x) => x * x;
static int AddOne(int x) => x + 1;
int[] values = { 1, 2, 3 };
Util.Transform(values, Square);//将Square方法作为参数传入Transform方法
foreach (int item in values)
{
Console.WriteLine(item.ToString());//循环输出1,4,9
}
Util.Transform(values, AddOne);
foreach (int item in values)
{
Console.WriteLine(item.ToString());//循环输出2,5,10
}
多播委托
所有的委托实例都具有多播的能力
一个委托实例可以引用一组目标方法
可以使用+和+=操作合并委托实例
委托的调用顺序和它们的定义顺序一致(与合并顺序一致)
public delegate int Transformer(int x);
class Program
{
static int Square(int x)
{
var result = x * x;
Console.Write($"执行平方后:{result} ");
return result;
}
static int Cube(int x)
{
var result = x * x * x;
Console.Write($"执行立方后:{result} ");
return result;
}
static void Main(string[] args)
{
Transformer t = null;
t += Square;
t += Cube;
var result = t(3);
Console.WriteLine("结果:" + result);
//输出"执行平方后:9 执行立方后:27 结果:27"
//如果多播委托的返回类型不是void,那么被调用者从最后一个被调用的的方法来接收返回值。
//前面的方法仍被调用,但结果被弃用
t -= Square;//从t中移除Square方法
Console.WriteLine("结果:" + result);//输出 "结果:27"
}
}
委托是不可变的,使用+=、-=操作符时,实际上是创建了新的委托实例,并将它赋值给当前的委托实例
多播委托(MulticastDelegate)继承自 Delegate ,表示多路广播委托;即,其调用列表中可以拥有多个元素的委托。实际上,我们自定义的委托的基类就是 MulticastDelegate。
C#会把作用于委托的+,-,+=,-=操作自动编译成System.Delegate的Combin和Remove两个静态方法
实例方法目标和静态方法目标
- 委托实例合并的一组方法,有的方法是实例方法(需要实例化对象后通过对象调用的),有的方法为静态方法 (static)
- 当一个实例方法被赋值给委托对象的时候,这个委托对象不仅保留着对方法的引用,还要保留着方法所属实例的引用
- System.Delegate的Target属性就代表着这个被调用的对象
- 如果引用的是静态方法,那么Target属性的值就是null
public delegate void ProgressReport(int percentComplete);
class X
{
public void InstanceProgress(int percentComplete) => Console.WriteLine(percentComplete);
}
class Program
{
static void Main(string[] args)
{
X x = new X();
ProgressReport p = x.InstanceProgress;
p(99);//99
Console.WriteLine(p.Target == x);//True
//输出委托实例的方法
Console.WriteLine(p.Method);//Void InstanceProgress(Int32)
}
泛型委托类型
委托类型可以包含泛型类型参数
public delegate T Transformer<T>(T arg);
class Util
{
public static void Transform<T>(T[] values, Transformer<T> t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
class Program
{
static int Square(int x) => x * x;
static void Main(string[] args)
{
int[] values = { 1, 2, 3 };
Util.Transform(values, Square);
foreach (var item in values)
{
Console.Write(item + " ");//1,4,9
}
}
}
Func和Action委托
使用泛型委托,就可以写出一组委托类型,它们可以调用的方法可以拥有任意的返回类型和任意数量的参数
Func无返回值
Action有返回值
与上则代码相比屏蔽了委托类型的定义,并将作为参数的泛型委托替换为Func,运行结果一致
//public delegate T Transformer<T>(T arg);
class Util
{
public static void Transform<T>(T[] values, Func<T, T> t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
class Program
{
static int Square(int x) => x * x;
static string WR(string x) => "WR:" + x;
static void Main(string[] args)
{
int[] values = { 1, 2, 3 };
string[] strArr = { "逆变", "协变" };
Util.Transform(values, Square);
foreach (var item in values)
{
Console.Write(item + " ");//1,4,9
}
Util.Transform(strArr, WR);
foreach (var item in strArr)
{
Console.Write(item + " ");//WR:逆变 WR:协变
}
}
}
委托与接口
委托可以解决的问题,接口都可以解决
什么情况下选择委托而非接口:
- 接口只能定义一个方法
- 需要多播能力
- 订阅者需要多次实现接口
委托的兼容性
委托类型
委托类型之间互不兼容,即使委托方法签名一样
public delegate int D1();
public delegate int D2();
D1 d1 = ()=>1;
D2 d2 = d1;//此句报错
委托实例
委托实例如果拥有相同的方法目标,那么委托实例就认为是相等的
static void test()
{
Console.WriteLine("");
}
D1 d1 = () => Console.WriteLine("");
D1 d2 = test;
D1 d3 = test;
Console.WriteLine(d1 == d2);//False
Console.WriteLine(d2 == d3);//True
参数
当你调用一个方法时,你提供的参数可以比方法定义的参数更加具体
委托的协变与逆变仅支持引用转换,不支持值类型转换,与泛型一致
delegate void stringAction(string s);
class Program
{
static void ActOnObj(object obj) => Console.WriteLine(obj);
static void Main(string[] args)
{
stringAction sa = new stringAction(ActOnObj);//带参实例化委托实例
sa("Hello World");
}
}
返回类型
调用方法得到的返回值类型可以比方法定义的返回值类型更加具体
delegate object ObjectRetriever();
class Test
{
static void Main(string[] args)
{
ObjectRetriever o = new ObjectRetriever(RetrieveString);
object res = o();
Console.WriteLine(res);//Hello
}
static string RetrieveString() => "Hello";
}