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
使用委派要特别注意执行的时机跟变数的值喔!