zoukankan      html  css  js  c++  java
  • 深入理解C# 委托(delegate)-戈多编程

    今天来谈谈委托,深入理解委托,本文来自各大神经验总结。

    1.委托是什么?

    委托类型的声明与方法签名相似。 它有一个返回值和任意数目任意类型的参数,是一种可用于封装命名方法或匿名方法的引用类型。 委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。

    (1)从数据结构来讲,委托和类一样是一种用户自定义类型

    (2)从设计模式来讲,委托(类)提供了方法(对象)的抽象

    既然委托是一种类型,那么它存储的是什么数据?

    我们知道,委托是方法的抽象,它存储的就是一系列具有相同签名和返回回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。

    2.委托类型的定义

    委托是类型,就好像类是类型一样。与类一样,委托类型必须在被用来创建变量以及类型对象之前声明。

    delegate void MyDel(int x);

    委托类型声明:

    (1) 以deleagate关键字开头。

    (2)返回类型+委托类型名+参数列表。

    3.申明委托变量

    MyDel del1,del2;

    4.初始化委托变量

    (1)使用new运算符

    new运算符的操作数的组成如下:

    • 委托类型名
    • 一组圆括号,其中包含作为调用列表中的第一个成员的方法的名字。方法可以是实例方法或静态方法。
    del1 = new MyDel( myInstObj.MyM1 );
    del2 = new MyDel( SClass.OtherM2 );

    (2)使用快捷语法

    del1 = myInstObj.MyM1;
    del2 = SClass.OtherM2;
    

    5.赋值委托

     由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的方法地址引用。旧的引用会被垃圾回收器回收。

    MyDel del;
    del = myInstaObj.MyM1; //委托初始化
    del = SClass.OtherM2;//委托重新赋值,旧的引用将被回收

    6.组合委托

    委托可以使用额外的运算符来组合。这个运算最终会创建一个新的委托,其调用列表是两个操作数的委托调用列表的副本的连接。

    委托是恒定的,操作数委托创建后不会被改变。委托组合拷贝的是操作数的副本

    MyDel del1 = myObj.MyMethod;
    MyDel del2 = SClass.OtherM2;
    MyDel del3 = del1 + del2;   //组合调用列表
    

    7.委托加减运算

    可以使用+=运算符,为委托新增方法。

    同样可以使用-=运算符,为委托移除方法。

    MyDel del = myObj.MyMethod;
    del += SClass.OtherM2; // 增加方法
    del -= myObj.MyMethod; // 移除方法

    8.委托调用

    委托调用跟方法调用类似。委托调用后,调用列表的每个方法将会被执行。

    在调用委托前,应判断委托是否为空。调用空委托会抛出异常。

    if(null != del)
    {
         del();//委托调用
    }

    9.匿名方法

    匿名方法是在初始化委托时内联声明的方法。

    基本结构:

    deleage( 参数 ) { 语句块 }

    例如:

    delegate int MyDel (int x); //定义一个委托 
    
    MyDel del = delegate( int x){ return x; };

    从上面我们可以看到,匿名方法是不会显示声明返回值的

    10.Lambda表达式

    ambda表达式主要用来简化匿名方法的语法。在匿名方法中,delegate关键字有点多余,因为编译器已经知道我们将方法赋值给委托。通过几个简单步骤,我们就可以将匿名方法转换为Lambda表达式:

    • 删除delegate关键字
    • 在参数列表和匿名方法主体之间防Lambda运算符=>。Lambda运算符读作"goes to"
    MyDel del = delegate( int x) { return x; };//匿名方法
    MyDel del2 = (int x) => {return x;};//Lambda表达式
    MyDel del3 = x => {return x};//简写的Lambda表达式

    11.委托示例

    public class Test
    {
    //定义委托
    public delegate void D_Math(int a, int b);
    public void Add(int a, int b)
    {
    Console.WriteLine("Add方法结果:{0}", a + b);
    }
    public void Cut(int a, int b)
    {
    Console.WriteLine("Cut方法结果:{0}", a - b);
    }
    }
    [TestClass]
    public class UnitTest1
    {
    [TestMethod]
    public void TestMethod1()
    {
    Test t = new Test();
    Test.D_Math D = new Test.D_Math(t.Add);//委托实例化,也可Test.D_Math D =t.Add;
    D += t.Cut;//委托可以以队列方式执行多个方法,以+=运算符或者-=来增加或者取消队列中的方法
    D(5, 6);

    }
    }

    以上看出来委托实用的地方了吗?即委托可以执行任何引入参数类型相同且返回类型相同的方法,甚至可以执行签名相同的方法队列。

    那么我们的方法签名(即引入参数和输出参数)真的必须与委托完全一致吗?答:不是的,我们不能忽略协变与逆变。

    我们这里简单介绍一下协变与逆变的知识。

    “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

    “逆变”则是指能够使用派生程度更小的类型。

    那么,我们的委托也是接受协变与逆变的。

    意思是,如果定义一个delegate,那么不仅仅签名完全相同的方法可以赋值给delegate变量。

    如果一个方法的参数表符合delegate声明,但返回的类型是(delegate声明返回类型)的派生类,那也可以将这个方法赋值给这个delegate变量。

    如果一个方法的返回类型符合delegate的声明,但参数是(delegate声明参数类型)的祖先类,那也可以将这个方法赋值给这个delegate变量。

    如果一个方法的参数和返回类型都符合上面两行的假设,那也可以将这个方法赋值给这个delegate变量。

    以下以两个简单示例解释协变与逆变:

    协变:

    public class A { }
        public class B:A { }//B继承自A
        public class Test
        {
            //定义委托
            public delegate A D_Math();
            public B Add()
            {
                return new B();
     
            }
            public A Add2() 
            {
                return new A();
            }
        }
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
                Test.D_Math d = new Test.D_Math(new Test().Add);//委托返回A,而Add方法返回B,此为协变。
            }
        }

    逆变:

    public class A { }
        public class B:A { }//B继承自A
        public class Test
        {
            //定义委托
            public delegate void D_Math(B b);
            public void Add(B b)
            {
     
            }
            public void Add2(A a) 
            {
     
            }
        }
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
                Test.D_Math d = new Test.D_Math(new Test().Add2);//委托引入参数B,而Add方法参数为A类型,此为协逆变。
            }
        }
  • 相关阅读:
    基于Metaweblog API 接口一键发布到国内外主流博客平台
    uva144 Student Grants
    Uva 10452
    Uva 439 Knight Moves
    Uva 352 The Seasonal War
    switch语句
    java——基础知识
    我的lua学习2
    codeforces 431 D. Random Task 组合数学
    codeforces 285 D. Permutation Sum 状压 dfs打表
  • 原文地址:https://www.cnblogs.com/geduocoding/p/7381061.html
Copyright © 2011-2022 走看看