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

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


<<:  Python产生QRCode图片 - Python练习题一

>>:  用电脑判断路段车辆数->控制红绿灯 小论文求解(急

R语言-正规化回归预测-ridge & lasso (ridge & lasso regression in r)

废话不多说,直接附上code 影片含有程序码详细解说,若有误再烦请告知,谢谢 library(glm...

【Day 29】- 应对反爬虫技术-综合篇

前情提要 昨天跟各位读者简介了反爬虫技术中,较常出现的验证码之应对方法。 开始之前 今天要跟各位介绍...

传说中的资讯安全全景图

标题的字每个都认识,合起来却感觉哪边怪怪的...有些漠生吗? 企业永续经营 = 资讯安全制度运行,2...

Day24:检查登入人数

国庆连假中,假日只想耍废玩 game,不想进修QQ,但为了避免断赛,还是加减推一些东西,等明後天再来...

Day 30 | 後记

最後一天了就来写个後记吧!感谢有看到最後的各位,能忍受我的超新手网页程序分享。铁人赛真的是一大挑战,...