1 概述:
代理(或者叫委托)是.NET版本的方法地址,类比于C++的函数指针,它是一个定义了返回值类型和参数值类型的类型安全的类(有点绕),代理类可以包含一个或者多个对方法的引用。
λ表达式直接和代理相关,当参数是代理类型时,你可以使用λ表达式来实现一个被代理所引用的方法。
2 声明代理:
1)delegate void MethodInvoker(int x);这里定义了一个名为MethodInvoker的代理,它保存了一个接受int类型的参数,返回值为void的方法引用。
2)代理以继承自System..MulticastDelegate的类的形式实现,基类为System.Delegate。C#编译器隐藏了代理类的操作细节。
3 使用代理:
1)创建代理的实例。
2)不论是静态方法还是不同类的实例方法,只要方法签名和代理匹配,就可以通过代理来调用。
3)当代理对象指向一个实例方法时,它不仅保存对该实例方法的引用,而且还保存该方法所属实例的引用,可以通过System.Delegate类的Target属性来获得该实例,如果是静态方法,则Target属性为null。
4泛型代理Action<T>和Func<T>:
1) Action<T>代理匹配没有返回值的方法或者说返回值为void的方法,它可以包含16个不同类型的参数。
比如Action匹配没有参数的方法,Action< in T1, in T2>匹配有两个参数的方法。
2) Func<T>代理匹配有返回值的方法,它也可以包含16个不同类型的参数。
比如:Func<out TResult>匹配没有参数,但返回值为TResult类型的方法。
Func<in T ,out TResult>匹配只有一个参数T,返回值为TResult类型的方法。
3)contravariance和covariance:
Action<T>支持contravariance:
比如:Action<object> x = ...;
Action<string> y = x;
Func<T>支持covariance:
比如:Func<string> x = ...;
Func<object> y = x;
5 多路代理的问题:
1)当使用多路代理时,依次调用相应的代理方法,如果其中有一个方法报异常,迭代将会中止。解决方法是:调用Delegate类的GetInvocationList()方法,返回Delegate的数组,然后依次遍历数组,进行异常的控制。
例如:
static void Main()
{
Action d1 = One;
d1 += Two;
Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
try
{
d();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
}
6 代理和接口:
1)可以使用代理解决的问题,同样也可以使用接口。
2)下面的情况优先使用代理:
(1)接口中只有一个方法。
(2)需要使用多路代理。
(3)同一个接口需要多次不同的实现。
7代理的兼容性:
1)即使两个代理的方法签名一样,它们也是不兼容的。
比如: delegate void D1();
delegate void D2();
D1 d1=method1;
D2 d2=d1;//编译错误。
但是:D2 d2=new D2(d1);//可行。
2)如果两个代理的实例指向相同的方法,则两代理实例相等。
3)参数的兼容性,代理可以有比目标方法更具体的参数类型。
比如:
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
}
}//当方法调用时,string类型隐式的向上转换成object类型。
4)返回值类型的兼容性:目标方法的返回值可以比代理的返回值更具体。
比如:
delegate object ObjectRetriever();
class Test
{
static void Main()
{
ObjectRetriever o = new ObjectRetriever (RetriveString);
object result = o();
Console.WriteLine (result); // hello
}
static string RetriveString() { return "hello"; }
}
7 匿名方法:
1)它是使用代理的另一种方式,通常用作为代理的参数。
static void Main()
{
string temp = "Hello";
Func < string, string > anonDel = delegate(string param)
{
param += temp;
param += " Terry !";
return param;
};
Console.WriteLine(anonDel("Begin:"));
}
2)使用匿名方法的优缺点:
优点:减少了代码量。
缺点:(1)在匿名方法内部不能使用跳转语句(break,goto和continue)来指向匿名方法的外部。反之依然,不能使用这些跳转语句指向匿名方法的内部。
(2)在匿名方法内部不能访问不安全的代码。
(3)匿名方法外部使用的ref 和out参数,不能在匿名方法内部被访问,而其外部定义的变量可以访问。
8 λ表达式:
static void Main()
{
string temp = "Hello";
Func < string, string > lambda = param=>
{
param += temp;
param += " Terry !";
return param;
};
Console.WriteLine(lambda("Begin:"));
}
2) λ表达式的参数问题:当有多个参数时,将参数以逗号分隔,放入括号中。
3)需要注意的是,当λ表达式包含一行语句时,可以省略大括号和return语句,如果包含多行语句,这大括号和return语句不可以省略。
Func<double, double> square = x => x * x;
Func<double, double> square = x =>
{
return x * x;
}以上完全等价。
3) λ表达式的变量捕捉问题,在for或者foreach中变量,C#将会把它们当做外部变量来处理。
比如:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);
foreach (Action a in actions) a(); // 333
9事件:
1) 事件基于代理,并提供了一个发布和订阅代理的途径。比如在windows应用中,按钮的点击事件,这种事件就是一个代理,当单击事件发生时,一个带有代理类型参数的方法将被调用。
2) 标准的事件模型:System.EventArgs,它是事件传输信息的基类。
3) Framework定义了一个泛型的代理:
public delegate void EventHandler<TEventArgs>(object source, TEventArgs e)
where TEventArgs : EventArgs;
定义好泛型代理的事件后,需要写一个受保护的虚方法