Delegates, events, lambda expres
2019-11-01 本文已影响0人
柒轩轩轩轩
Delegates 委托
委托是一个对象,它知道如何调用一个方法
委托类型和委托实例
- 委托类型定义了委托实例可以调用的那类方法,具体来说,委托类型定义了方法的返回类型和参数
delegate int Transformer (int x); //define argument type is int, and return type is int 委托类型
static int Square (int x) {return x *x} // 上面的委托类型可以调用这个方法,因为类型一致
- 把方法赋值给委托变量的时候就创建了委托实例
Transformer t = Square; - 调用
int answer = t(3); // answer is 9
public class Program
{
delegate int Transformer(int x);
static int Square(int x) => x * x;
static void Main ()
{
Transformer t = Square; //Create delegate instance
int result = t(3); //Invoke delegate
Console.WriteLine(result);
}
static int Square (int x) => x *x;
}
委托实例
- 委托的实例实例其实就是调用者的委托: 调用者调用委托,然后委托调用目标方法
- 间接地把调用者和目标方法解耦合了
- 简写 Transformer t = Square; ==> Transformer t = new Transformer (Square);
- 简写 t(3) ==> t.Invoke(3)
编写插件式的方法
- 方法是在运行时才赋值给委托变量的
public delegate int Transformer(int x);
class Util
{
public static void Transform(int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i ++)
{
values[i] = t(values[i]);
}
}
}
public class Program
{
static int Square(int x) => x*x;
static void Main()
{
int[] values = {1,2,3};
Util.Transform(values, Square);
foreach (int i in values)
{
Console.WriteLine($"{i}");
}
}
}
多播委托
所有的委托实例都具有多播的能力,一个委托实例可以引用一组目标方法
- 和+= 操作符可以合并委托实例
SomeDelegate d = SomeMethod1 ;
d += SomeMethod2;
- 和+= 操作符可以合并委托实例
-
调用d就会调用SomeMethod1 和 SomeMethod2
-
委托的调用顺序与他们的定义顺序一致
-
-和 -=会把右边的委托从左边的委托里移除
d -= SomeMethod -
委托变量使用+ 或者 += 操作符时,其操作数可以是null,就相当于把一个新的赋值给了委托变量
SomeDelegate d = null;
d += SomeMethod1;
相当于
d = SomeMethod1 -
对单个目标方法的委托变量使用 -=操作符时,就相当于把null值赋给了委托变量
-
委托是不可变的
-
使用+=或-=操作符时,实际上是创建了新的委托实例, 并把它赋给当前的委托变量
-
如果多播委托的返回类型不是void,那么调用者从最后一个被调用的方法来接收返回值。前面的方法仍然会被调用,但是其返回值就被弃用了
实例方法目标和静态方法目标
- 当一个实例方法被赋值给委托对象的时候,这个委托对象不仅要保留着对方法的引用,还有保留着方法所属实例的引用
- System.Delegate 的Target属性就代表着这个实例
- 如果引用的是静态方法,那么Target属性的值就是null
public delegate void ProgressReporter (int percentComplete)
class X
{
public void InstanceProgress (int percentComplete)
=> Console.WriteLine(percentComplete);
}
class Program
{
static void Main()
{
X x = new X();
ProgressReporter 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);
public 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 Test
{
static void Main()
{
int [] values = {1,2,3};
Util.Transform(values, Square);
foreach (int i in values)
Console.Write(i + " "); //1 4 9
}
static int Square(int x) => x* x;
}
Func 和 Action委托
使用泛型委托,就可以写出这样一组委托类型,他们可调用的方法可以拥有任意的返回类型和任意(合理)数量的参数
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 args)
delegate void Action ();
delegate void Action <in T> (T arg);
delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
public 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]);
}
}
委托 vs 接口
- 委托可以解决的问题, 接口都可以解决
- 什么情况下更适合使用委托而不是接口呢,当下列条件之一满足时:
1 接口只能定义一个方法
2 需要多播能力
3 订阅者需要多次实现接口
委托的兼容性 - 委托类型
委托类型之间互不相容,即使方法签名一样:
delegate void D1();
delegate void D2();
D1 d1 = Method1;
D2 d2 = d1; //Compile-time error
委托的兼容性 - 委托实例
如果委托实例拥有相同的方法目标,那么委托实例就认为是相等的
delegate void D();
...
D d1 = Method1;
D d2 = Method1;
Console.WriteLine(d1 == d2); //True
委托的兼容性 - 参数
- 当你调用一个方法时, 你提供的参数(argument)可以比方法的参数定义更具体
- 委托可以接受比它的方法目标更具体的参数类型,这个叫ContraVariance
delegate void StringAction (string s);
class Test
{
static void Main()
{
StringAction sa = new StringAction(ActOnObject);
sa("hello");
}
static void ActOnObject (object o) => Console.WriteLine(o); //hello
}
委托的兼容性- 返回类型
- 调用方法时,你可以得到一个比请求的类型更具体的返回类型
- 委托的目标方法可以返回比委托描述里更具体的类型的fan hu