第4章 高级特性——委托、事件、匿名方法和Lambda表达式

4.1 委托#

什么是委托?#

委托是一种知道调用方法的对象。

复制代码
delegate int Dele(int x);

class Test{
    static int Square(int x)=>x*x;    

    static void Main(){
        Dele d=Square;
        int result=d(4);// 16
    }
}
复制代码

多播委托#

所有的委托实例都拥有多播能力。即一个委托实例可以引用一组目标方法。

  • 使用 += -=
复制代码
delegate void Dele(int x);

class Test{
    static void Square(int x)=>Console.Write(x*x);    
  static void Cube(int x)=>Console.Write(x*x*x);    

    static void Main(){
        Dele d=Square;
        d+=Cube;
        d(4);// 16  64
    }
}
复制代码

如果委托的方法组有返回值,则委托实例返回最后一个委托方法的返回值。

泛型委托#

委托类型可以包含泛型委托类型参数。

复制代码
public delegate T Dele<T>(T arg);

public class Util{
    public static void Transform<T>(T[] values,Dele<T> d){
        for(int i=0;i<values.Length;i++){
            values[i]=d(valuse[i]);
        }
    } 
} 

public class Test{
    static int Square(int x)=>x*x;

    static void Main(){
        int[] ints={1,2,3};
        Util.Transform(ints,Square);

        foreach(int i in ints){
            Console.Write(i+" ");// 1 4 9
        }
    }
}
复制代码

Func和Action委托#

通用的小型委托类型,具有任意返回类型和(合理的)任意数目的参数。

 

对比上例:

复制代码
public class Util{
    public static void Transform<T>(T[] values,Func<T,T> d){
        for(int i=0;i<values.Length;i++){
            values[i]=d(valuse[i]);
        }
    } 
} 

public class Test{
    static int Square(int x)=>x*x;

    static void Main(){
        int[] ints={1,2,3};
        Util.Transform(ints,Square);

        foreach(int i in ints){
            Console.Write(i+" ");// 1 4 9
        }
    }
}
复制代码

委托与接口#

能用委托解决的问题,都可以用接口解决。

对比上例:

复制代码
public interface ITransformer{
    int Transformer(int x);
}

public class Util{
    public static void TransformAll(int[] values,ITransformer t){
        for(int i=0;i<values.Length;i++){
            values[i]=t.Transformer(valuse[i]);
        }
    } 
} 

public class Squarer:ITransformer{
    public int Transformer(int x)=>x*x;    
}

public class Test{
    static void Main(){
        int[] ints={1,2,3};
        Util.TransformAll(ints,new Squarer());
        foreach(int i in ints){
            Console.Write(i+" ");// 1 4 9
        }
    }
}
复制代码

优先使用委托的情况:

(1)接口内仅定义了一个方法

(2)需要多播能力

(3)订阅者需要多次实现接口

委托的兼容性#

即使签名相似,委托类型也互不兼容。

delegate void D1();
delegate void D2();

D1 d1=Method;
D2 d2=d1 // 编译错误
D2 d2=new D2(d1);

如果委托实例指向同一个方法,则认为它们是等价的。

delegate void D();

D d1=Method();
D d2=Method();
Console.WriteLine(d1==d2); // true

委托可以比指向的目标方法的参数更具体化,这称为逆变

复制代码
delegate void StringAction(string s);

class Test{
    static void Method(object o)=>Console.WriteLine(o);

    static void Main(){
        StringAction sa=new StringAction(Method);
        sa("hello"); // hello
    }
}

// 这里object->string->object,后部分向上转型
复制代码

指向的目标方法的参数可以比委托更具体化,这称为协变

复制代码
delegate object StringAction();

class Test{
    static string Method()=>Console.WriteLine("hello");

    static void Main(){
        StringAction sa=new StringAction(Method);
        object o=sa;
        Console.WriteLine(o); // hello
    }
}

// 这里string->object->string,后部分向下转型
复制代码

自定义泛型委托,最好使用下列标准(为了类型的关系自然的转化):

(1)将只用于返回值类型的类型参数标记为协变(out

(2)将只用于参数的任意类型参数标记为逆变(in

delegate TResult Func<out TResult>();
Func<string> x=...;
Func<object> y=x;// string->object

delegate void Action<in T>(T arg);
Func<object> x=...;
Func<string> y=x;// object->string

4.2 事件#

什么是事件?#

public delegate void TestHandler();

public class Test{
    public event TestHandler TestChanged;
} 

事件修饰符:virtual、override、abstract、sealed

事件有什么用?#

上例中,TestChanged在Test类内当作一个委托使用。

而Test类外面的TestHandler委托,只能在TestChanged事件身上进行+=、-=操作。

这样做的目的就是为了保证订阅者之间不互相影响。提高健壮性

4.3 匿名方法#

什么是匿名方法?#

没有方法名的方法,只能用一次,必须是一个语句块 { }

delegate int Dele(int i);

Dele d=delegate (int x) {return x*x;};
Console.WriteLine(d(4)); // 16

4.4 Lambda表达式#

什么是Lambda表达式?#

Lambda表达式是一种可以替代委托实例的匿名方法。

对比上例:

delegate int Dele(int i);

Dele d1= (int x)=> {return x*x};
Console.WriteLine(d1(4)); // 16

Dele d2= x=> x*x;
Console.WriteLine(d2(4)); // 16

形式:

(参数) => 表达式或者语句块

  • 为了方便,只有一个可推测类型的参数时,可以省略小括号。

Lambda表达式通常和Func和Action委托一起使用。对比上例:

Func<int,int> d= x=>x*x;

显示指定Lambda参数的类型#

编译器可以根据上下文推断出Lambda表达式的类型,但是当无法判断时就必须显示指定每一个参数的类型。

void Foo<T>(T x){}
void Bar<T>(Action<T> x){}

//Bar(x=>Foo(x)); // ??无法判断
Bar((int x)=>Foo(x));
Bar<int>( x=>Foo(x) );
Bar<int>(Foo);

捕获外部变量#

Lambda表达式可以引用方法内定义的局部变量和方法的参数。

  • 这些变量和外部参数称为捕获变量,捕获变量的表达式称为闭包

捕获变量在真正调用委托时赋值,而不是在捕获时赋值。

Lambda表达式可以更新捕获的变量的值。

捕获变量的生命周期和委托的生命周期一样,委托执行完毕后,捕获变量会从作用域消失,变量值会变回去。

Lambda表达式和局部方法的对比#

局部方法和Lambda表达式的相应功能是重叠的。

局部方法的优势:

(1)局部方法可直接调用自己(递归)

(2)局部方法避免了定义杂乱的委托类型

(3)局部方法的开销更小

 

posted @   不爱菠萝的菠萝君  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
主题色彩
点击右上角即可分享
微信分享提示