C# delegate 委派

IT邦第二篇 就献给委派了

记得当年第一次看到 += 这东西的时候

问问前辈这是什么

前辈只有跟我说 : 委派 很可怕 不要用~~~

真正深入了解之后才觉得相见恨晚啊!

先从委派最正规的方式写起(但我几乎不用这种方式...)

public delegate void DoSomething(int number);

先宣告一个委派的签章 这个签章代表晚点要赋予它的方法

必须符合 无回传值(void) 且 具有一个参数 int

换句话说 如果宣告成如下

public delegate int ParseSomething(string str);

就代表 该方法必须回传int 且 具有一个参数 string

public static void PrintNumber(int n){    Console.WriteLine(n);}public static void SquareAndPrintNumber(int n){    n *= n;    Console.WriteLine(n);}static void Main(string[] args){    var something = new DoSomething(PrintNumber);    something.Invoke(5);    something(6);}

我宣告了两个方法 PrintNumber 跟 SquareAndPrintNumber

都符合DoSomething的签章 (无回传 且具有一个参数int)

在这个範例中 先使用PrintNumber

var something = new DoSomething(PrintNumber);就是建立一个委派 并把PrintNumber传入

something.Invoke(5);就是真正去执行这个委派 PrintNumber 所以最后会印出 5

something(6); 是执行委派的另外一个方式 所以会印出6

我们修改一下程式码 将 PrintNumber更换成 SquareAndPrintNumber

static void Main(string[] args){    var something = new DoSomething(SquareAndPrintNumber);    something.Invoke(5);    something(6);}

这时候要执行的方法就会变成SquareAndPrintNumber

所以印出来会是 25 36

我们再做一个实验 如果有两个委派 具有相同的签章 他们是否可以互通呢?

我们修改一下程式码 新增一个DoSomething1 跟 DoSomething一模一样 只是名称不同

再修改一下呼叫委派的方式

public delegate void DoSomething1(int number);public static void ExeDoSomething(DoSomething something)//呼叫委派的方式{    something.Invoke(5);    something(6);}static void Main(string[] args){    {        var something = new DoSomething(PrintNumber);        ExeDoSomething(something);    }    {        var something = new DoSomething1(PrintNumber);        ExeDoSomething(something);//error    }}

会发现 就算签章一样 只要宣告的委派不同 他们之间是不可以互相转换的~相当的严谨

照上面正规委派写法 我可能一个方法就必须要建立一个public delegate void DoSomething1(int number);这种委派签章

又臭又长 使用上又不方便

接下来 就是巨硬的德政了!Action/Func

Action/Func 是泛型的委派(泛型之后会再开一篇来讲 不要在这边乱开副本!)

我如果需要一个无回传值且具有一个int参数的方法 我不需要从头宣告一个

public delegate void DoSomething1(int number);来用

我只要写Action 就可以了

Action                      action1;//无回传值无参数Action<int>                 action2;//无回传值具有一个int参数Action<int, string>         action3;//无回传值具有int string参数Func<int>                   func1;//回传int 无参数Func<string,int>            func2;//回传int 具有一个string参数Func<int,string,DateTime>   func3;//回传DateTime具有int string参数

上面这些宣告可以自行体会一下

我们可以很容易地知道 Action就是无回传值的委派 而Func就是有回传值的

其余用法几乎没有差异

附带一提 以前最多支援到8个型别参数 也就是说我最多可以写

Action<int,string,double,float,Datetime,long,List,bool> //应该没人会这样写..吧?

但刚刚看了一下程式码.Net6 已经可以支援到16个参数型别了呢!(洒花?

回归正题

修改一下程式码 将 ExeDoSomething的参数从 DoSomething 更换成 Action

public static void ExeDoSomething(Action<int> something){    something.Invoke(5);    something(6);}static void Main(string[] args){    ExeDoSomething(PrintNumber);    ExeDoSomething(SquareAndPrintNumber);}

因为 PrintNumber 跟 SquareAndPrintNumber 都是符合 无回传值 有一个int参数的方法签章

所以他们都可以当作参数传入ExeDoSomething

执行结果会是 5, 6, 25, 36

喔~ 可是还是好烦阿 要把方法当参数传入 我必须要建立一个方法才能这样做

想名字应该是程序猿永远的痛吧..

还好还好 巨硬的德政 匿名委派 我们可以这样写

上面那两个方法可以砍掉了(PrintNumber/SquareAndPrintNumber)

static void Main(string[] args){    ExeDoSomething((int n) =>     {        Console.WriteLine(n);    });    ExeDoSomething((int n) =>    {        n *= n;        Console.WriteLine(n);    });}

(int n) 这边可以想成就是方法后面的参数签章 前面没有名字 => 后面是方法主体

这种lambda写法在巨硬很常使用,建议要习惯一下!

喔喔喔喔!! 不用想名字了真好~ 刚刚光想PrintNumber/SquareAndPrintNumber 就花了我两小时呢!

修但几累!

巨硬表示 我觉得这样看起来还是有点蠢

因为ExeDoSomething 里面的参数就已经知道 Action的参数是int 为什么你外面还要写一次?

    ExeDoSomething((n) =>     {        Console.WriteLine(n);    });

巨硬表示 : 我觉得只有一个参数n还要加括号有点蠢

    ExeDoSomething(n =>     {        Console.WriteLine(n);    });

巨硬表示 : 程式本体只有一行 应该可以再精简吧?

ExeDoSomething(n => Console.WriteLine(n));

流浪汉表示 : (n)后面没加分号 (这里不用加啊!!!!!

这种写法在巨硬称做lambda表达式

上面这种写法 有几个限制

第一 参数要一个才能省略参数的括号

第二 如果没参数的画 一定要有括号

第三 程式码如果只有一行 可以省略该行的分号以及程式本体的大括号({})(分号 大括号必须同时存在/省略)

参数型别都可以省略

Func<int> func = () => 1;//因为无参数 所以都是回传1;Func<int,int> func2 = x => x+1;//具有参数 回传 x+1

委派基础就到这边!

下一篇预计会开委派的实作

因为知道委派但是不知道怎么去活用它还是没用阿!!!

------3/8号补充-----

本来一直记得要讲这个重要的东西

但居然忘记了...Orz(这年代还有人用这个吗?

委派的 闭包问题

先上Code

static void Main(string[] args){    Action action = null;    for(int i = 0; i < 10; i++)    {        action += () => Console.WriteLine(i);    }    action();}

会印出什么?

0 1 2 3 4 5 6 7 8 9 ??

不对

会是

10 10 10 10 10 10 10 10 10 10

Why?

因为委派的方法是 印出i

但我只是去设定委派内容

真正在执行的是 action(); 这一行

而i跑完迴圈 for(int i = 0; i < 10; i++) 跳离迴圈时会是10

所以真正执行的时候 是印出 10

那我真正要印出0~9怎么办?

给他一个暂存变数即可

static void Main(string[] args){    Action action = null;    for(int i = 0; i <10;i++)    {        var temp = i;        action += () => Console.WriteLine(temp);    }    action();}

这样 其实记忆体会产生10个temp 而每个temp分别就是 0-9

最后执行的时候就是执行印出各自的temp

使用委派要特别注意执行的时机跟变数的值喔!


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章