C#沉淀-委托

什么是委托

可以认为委托是持有一个或多个方法的对象。委托可以被执行,执行委托时委托会执行它所“持有”的方法

代码示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //使用关键字delegate声明委托类型
    //委托是一种类型,所以它与类属于同一级别
    //注意:这里是委托类型,而不是委托对象
    delegate void MyDel(int value);

    class Program
    {

        void PrintLow(int value)
        {
            Console.WriteLine("{0} - Low Value", value);
        }

        void PrintHigh(int value)
        {
            Console.WriteLine("{0} - High Value", value);
        }

        static void Main(string[] args)
        {
            //实例化Program类,以访问PrintLow和PrintHigh方法
            Program program = new Program();

            //声明一个MyDel类型的委托
            MyDel del;

            //创建随机数
            Random rand = new Random();
            int randomvalue = rand.Next(99);

            //使用三目运算符根据当前随机数的值来创建委托对象
            del = randomvalue < 50
                ? new MyDel(program.PrintLow)
                : new MyDel(program.PrintHigh);

            //执行委托
            del(randomvalue);

            Console.ReadKey();
        }
    }
}

从上例可以看出,使用委托的首先得通过关键字delegate声明一个委托类型,这个委托类型包括返回值、名称、签名;当类型声明好以后,需要通过new来创建委托对象,创建对象时的参数是一个方法,这个方法的签名和返回类型必须与该委托类型定义的签名一致;调用委托时,直接通过实例化的委托对象名,并提供参数即可,然后委托会执行在其所持有的方法

委托与类

委托和类一样,是一种用户自定义的类型;不同的是类表示的是数据和方法的集合,而委托持有一个或多个方法,以及一系列预定义操作

委托的使用步骤

  • 声明一个委托类型
  • 使用该委托类型声明一个委托变量
  • 创建委托类型的对象,把它赋值给委托变量;委托对象中包括指向某个方法的引用,此方法和委托类型定义的签名与返回类型需要一致
  • 增加更多的方法(可选)
  • 像调用方法一样调用委托(委托中的包含的每一个方法都会被执行)

delegate的原则

delegate相当于一个包含有序方法列表的对象,这些方法都具有相同的签名和返回类型

  • 方法的列表称为调用列表
  • 委托保存的方法可以来自任何类或结构,只要它们在以下两点匹配:
    • 委托的返回类型
    • 委托的签名(包括ref和out修饰符)
  • 调用列表中的方法可以是静态方法也可以是实例方法
  • 在调用委托的时候,会调用列表中的所有方法

声明委托类型

如下,delegate关键字开关,然后是返回类型,再定义名称与签名

delegate void MyDel(int vallue);

返回类型与签名指定了委托接受的方法形式

注意:委托类型是没有方法主体的

创建委托对象

使用new运算符创建对象

MyDel del = new MyDel(object.Func); //object.Func是个实例方法
Mydel _del = new MyDel(Object.Func); //Object.Func是个静态方法

使用快捷语法创建对象

MyDel del = object.Func; //object.Func是个实例方法
Mydel _del = Object.Func; //Object.Func是个静态方法

这种语法是能够工作是因为在方法名称和其相应的委托类型之间存在隐式的转换

创建委托对象后会将指定的方法加入到委托的调用列表中

由于委托是引用类型,可以通过赋值来改变包含在委托变量中的引用,如下:

MyDel del;
del = new MyDel(object.FuncA); //创建第一个对象
del = new MyDel(object.FuncB); //创建第二个对象

由于第二个对象也赋值给了变量del,因此del所引用的第一个对象将被垃圾回收器回收

组合委托

//创建两个委托
MyDel del_A = new MyDel(object.FuncA);
Mydel del_B = new MyDel(object.FuncA);

//组合委托
MyDel del_C = del_A + del_B;

当将del_A与del_B通过+进行组合后,会返回一个新的委托对象,该对象将del_A与del_B中的方法调用列表组合到新的对象里,该新对象赋值给变量del_C,所以执行del_C的时候,会执行del_A与del_B中所保存的方法object.FuncA和object.FuncA

委托添加多个方法

MyDel del = object.FuncA; //创建并初始化委托对象
del += object.FuncB; //增加方法
del += object.FuncC; //增加方法

通过+=符号为委托对象添加更多方法,上例中,del对象不保存了三个方法,在执行del时,这三个方法会被依次调用

注意,在使用+=为委托对象添加新的方法时,实际上是创建了一个新的委托对象(原对象的副本)

移除委托方法

del -= object.FuncB; //移除方法
del -= object.FuncC; //移除方法

通过-=来将委托调用列表中已保存的方法,移除动作是从调用列表的最后一个方法开始匹配,一次只会移除一条匹配的方法,如果调用列表中不存在该方法,则没有任何效果;如果试图调用一个空的委托则会发生异常

注意,在使用-=为委托对象移除方法时,实际上是创建一个新的委托对象(原对象的副本)

调用委托

调用委托就像调用方法一样

示例:MyDel类型参考上面的定义

MyDel del = object.FuncA; //创建并初始化委托对象
del += object.FuncB; //增加方法
del += object.FuncC; //增加方法

//调用委托 
del(55);

参数55会在调用委托对象时依次传递给保存的方法

一个完整的委托示例代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    class Program
    {
        //定义委托类型
        delegate void PrintFunction(string txt);

        //测试类中定义三个方法
        class Test
        {
            public void PrintA(string txt)
            {
                Console.WriteLine("printA:{0}", txt);
            }

            public void PrintB(string txt)
            {
                Console.WriteLine("printB:{0}", txt);
            }

            public static void PrintC(string txt)
            {
                Console.WriteLine("printC:{0}", txt);
            }
        }
        static void Main(string[] args)
        {
            Test test = new Test();
            PrintFunction pf;

            //实例化并创建委托对象
            pf = test.PrintA;

            //为委托对象增加方法
            pf += test.PrintB;
            pf += Test.PrintC;
            pf += test.PrintA; //添加一个重复的方法

            //通过与null比较,确认委托对象中保存了方法
            if (pf != null)
                pf("Hello");
            else
                Console.WriteLine("pf是个空委托!");

            Console.ReadKey();
        }
    }
}

调用带有返回值的委托

如何委托有返回值,并且调用列表中有一个以上的方法,那么将使用最后一个方法的返回值,之前方法的返回值被忽略

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    class Program
    {
        //定义委托类型
        delegate int DelFunction();

        //测试类中定义三个方法
        class Test
        {
            int IntValue = 0;
            
            public int FuncA()
            {
                return IntValue += 1;
            }

            public int FuncB()
            {
                return IntValue += 10;
            }
        }
        static void Main(string[] args)
        {
            Test test = new Test();
            DelFunction df;

            df = test.FuncA;
            df += test.FuncB;

            //最终返回值的是11
            if (df != null)
                Console.WriteLine("返回值:"+df());
            else
                Console.WriteLine("pf是个空委托!");

            Console.ReadKey();
        }
    }
}

具有引用参数的委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();

            MyDel del = Add1;
            del += Add2;

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

在调用Add1方法时,x = 5+1,再调用Add2方法时,不是x = 5+2而是x = 6 +2

参考:ref按引用传递参数

在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象

匿名方法

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();
            
            //采用匿名方法形式代替具名方法
            MyDel del = delegate(ref int y) { y += 3; };
            del += Add1;
            del += Add2;

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

在声明委托变量时作为初始化表达式,或在为委托增加事件时使用

语法解析

以关键字delegate开头;后跟小括号提供参数;再后跟{}作为语句块

delegate (Parameters) {ImplementationCode}

  • 匿名方法不会显示的声明返回类型delegate (int x) { return x;}即为返回一个int类型的值
  • 参数的数量、位置、类型、修饰符必须与委托相匹配
  • 可以通过省略圆括号或使圆括号为空来简化匿名方法的参数列表,前提是参数是不包含out参数,方法体中不使用任何参数

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();
            
            //采用匿名方法形式代替具名方法
            MyDel del = delegate(ref int y) { y += 3; };
            del += Add1;
            del += Add2;
            
            //匿名方法未使用任何参数,简化形式
            del += delegate{int z = 10;};

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

如果定义一个带有params形式的参数,在使用匿名方法的时候可以省略params关键字以简化代码

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义一个带有params形式参数的委托类型
    delegate void DelFunction(int x, params int[] z);
    class Program
    {
        static void Main(string[] args)
        {
            Program program = new Program();
            
            // 关键字params被忽略(省略关键字以简化)
            DelFunction df = delegate(int x, int[] y) { ... };

            Console.ReadKey();
        }
    }
}

Lambda表达式

Lambda可以简化匿名方法,语法形式如下:

(参数) => {语句块} // => 读作 gose to
  • 参数中的类型可以省略
  • 如果只有一个参数,圆括号可以省略
  • 如果没有参数,圆括号不可以省略
  • 语句块如果只有一行代码,花括号可以省略

示例:

MyDel del = delegate(int y) { return y += 3; }; //匿名方法
MyDel del1 = (int y) => {return y += 3;} // Lambda表达式
MyDel del2 = (y) => {return y += 3;} // 省略参数类型
MyDel del3 = y => y += 3; // 省略圆括号和花括号,虽然没有return,但仍会返回y的值

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容