C# 委托、匿名方法、Lambda表达式总结

1. 委托

从数据结构来看,委托是一种用户自定义的类型,不同于class ,用delegate标记,有返回值和形参。
从使用场景来看,委托是一种方法指针,它存储的是一系列具有相同参数和返回类型的方法的地址。调用委托时所有方法将被执行。

1.1委托的声明

• 委托是一种特殊的类型,虽然是对方法的抽象,但是定义委托时和类相似,在任何可以定义类的地方都可以定义委托;也就是说可以声明在命名空间和类内部,不能在方法内部声明。
• 访问级别与类定义时的访问级别一致。
• 委托的定义必须以;结束。
• 【建议】工程化项目中,委托声明时的命名风格通常以Handler结尾而不是Delegate,它表示一个方法处理。

// 委托的声明形式
[访问修饰符] delegate 返回值类型 委托名称(形参列表);
// 例如
  public delegate void MyDelegate1(string p1);// 它表示指向 返回类型是void 参数是string 类型的方法。
// 没有参数时 ()不能省略;
// 命名空间下,默认访问级别是internal
    delegate void MyDelegate2();
// 以下都是合法的声明
    public delegate void MyDel(string a);
  delegate Action MyDel3();
  delegate Func<string, MyDel> MyDel4(Action param);
  delegate Action MyDelgate3();

1.2 委托的实例化和调用

• 委托是一种特殊的类,它必须直接或间接的继承自System.Delegate.
• 委托可以按如下方式实例化以及调用

// 声明委托
public delegate void MyHandler(string inputStr);
// 在方法中实例化和调用委托
        static void Main(string[] args)
        {
            // 实例化委托,就是建立委托类型与方法关联
            MyHandler myHandler = Method1;
            myHandler("ok");//同步执行
            myHandler.BeginInvoke("ok",null,null);//异步执行,并通过回调来call 主线程方法.
            myHandler.Invoke("ok"); //同步调用
        }
        static void Method1(string str)
        {
            Console.WriteLine(str);
        }
        static void Method2(string str)
        {
            Console.WriteLine(str);
        }

1.3 通用委托System.Func 和System.Action

• 为了简化自定义委托类型声明,并统一委托声明方式,.Net 3.5 提供了一组通用的委托,其中大多数是泛型。
• System.Func 表示有返回值的方法委托;
• System.Action表示返回为void 的方法委托。
• 声明的源码如下:

public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1,in T2>(in T1 arg1,in T2 arg2);  
public delegate void Action<in T1,in T2,in T3>(in T1 arg1,in T2 arg2,in T3 arg3);
public delegate void Action<in T1,in T2,in T3,in T4>(in T1 arg1,in T2 arg2,in T3 arg3,in T4 arg4);
…………
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T,out TResult>(T arg);
public delegate TResult Func<in T1,in T2,out TResult>(T1 arg1,T2 arg2);
public delegate TResult Func<in T1,in T2,in T3,out TResult>(T1 arg1,T2 arg2,T3 arg3);
public delegate TResult Func<in T1,in T2,in T3,in T4,out TResult>(T1 arg1,T2 arg2,T3 arg3,T4 arg4);
…………

• 大多数情况下,平台为我们提供的委托类型能完全避免我们自己定义自定义的委托类型,然而,如果想要显著增强代码的可读性,还是应该声名自己的委托类型。

1.4 委托的逆变和协变

1.4.1 概念

协变和逆变来源于类型和类型之间的绑定,C#4.0开始在泛型的接口和委托上支持协变和逆变,不过在这个版本之前的委托也是支持协变和逆变的。
如果有类型Parent和其子类Sub,那么Parent p=new Sub();这种的类型转换是安全的。如果有一种类型和Parent类型进行了绑定,比如说Parent[]数组,如果Sub[]到Parent[]的转换是安全的,我们就说是协变,如果相反的方向上转换是安全的,我们就说是逆变了。
C#的协变和逆变没有完整的阐述这个数学和物理上的概念,C#为协变和逆变的实现捆绑了很多条件:
①必须是泛型的委托或接口
②协变只能支持返回类型
③逆变只能支持参数
而这些条件总归是实现了一个面向对象原则:里氏替换原则。也就是说,不管是协变还是逆变,最终都是一种类型安全的转换,没有不安全的转换发生。

// 协变 :和谐的变化
// 下面这个定义可以说明协变,在定义object 基类型的地方,可以传入string 派生类型的实例.
object[] myArray = new string[] { "abc", "def", "ghi", // ... };
// 逆变:不正常的变化 指从派生类到基类的变化
// 数组不支持逆变性,我们以泛型接口描述
// in 是C# 关键字,表示泛型参数类型支持逆变
interface IContravariant<in A> { }  // 一个支持逆变的泛型接口
interface IExtContravariant<in A> : IContravariant<A> { }// 派生接口
class Sample<A> : IContravariant<A> { } // 基接口的实现
class Program
{
    static void Test()
    {
        // 实例化object类型的  IContravariant 接口
        IContravariant<Object> iobj = new Sample<Object>();
        // 实例化object类型的  IContravariant 接口
        IContravariant<String> istr = new Sample<String>();
        
        // 下面这一句不会报错,因为泛型参数In 表示支持逆变,可以实现从特殊类型到基础类型的变化。
        istr = iobj;
    }
}
1.4.2 委托中的逆变和协变

• 在.Net framework 2.0之后的版本,委托支持对参数的逆变性和对返回值的协变性。
• 参数的逆变性:由于Action 的定义中,参数增加了对逆变的支持,所以 如果将Action 的委托赋值给Action

posted @ 2020-10-27 12:41  aimigi  阅读(131)  评论(0编辑  收藏  举报