包装成变量
我们先来看委托的用法
using System;
namespace CSharplearn
{
class Program
{
public delegate void Remainder(int num);//声明委托
static void Main(string[] args)
{
Remainder remainder = new Remainder(MethodRemainder/*传给委托的方法*/);//实例化委托
remainder(10);//调用委托里的方法
}
public static void MethodRemainder(int num) //声明方法
{
if (num % 2 == 0) //方法主体
{
Console.WriteLine("这个整数取余数是0");
}
}
}
}
你要用委托,肯定要先声明委托,声明委托的方式是
作用域修饰符 delegate 返回值类型 委托名(参数);
声明一个委托和声明一个方法差不多,区别在于多了一个delegate,以及不需要方法主体.
声明委托可以在任何地方,除了方法内,当然,注意作用域.
调用委托必须实例化委托,也就是new一个委托.委托需要传一个方法给变量,现在我们来写这个方法.
如果这个方法要传给委托,那么他的返回值和参数要和委托一样.
然后我们写一个MethodRemainder方法,他会判断传进来到参数取余数是不是等于0如果是就打印"这个整数取余数是0".
现在在new Remainder()的()里面填上MethodRemainder,不要在方法后面加(),因为是传方法,而不是调用这个方法.
写完Remainder remainder = new Remainder(MethodRemainder)后,你就可以通过remainder(参数)来调用里面的方法.
结果:
然后我们就知道了委托的第一个作用,可以把方法包装成变量(在这里就是Remainder类的变量remainder)
于是你就会产生所有初学委托的人都会有的疑问,为什么调用一个方法要这么费劲,我直接调用方法名他不好吗?
要解释这个疑问,还需要看下一个例子
解耦与封闭
我们先把委托去掉,然后把取余数方法移到另一个文件去
文件一:Program.cs
namespace CSharplearn
{
class Program
{
static void Main(string[] args)
{
RemainderClass.MethodRemainder(10);
}
}
}
文件二:RemainderClass.cs
using System;
class RemainderClass
{
public static void MethodRemainder(int num)
{
if (num % 2 == 0)
{
Console.WriteLine("这个整数取余数是0");
}
}
}
运行结果和上面一样就不展示了
现在我们需要多几种方式来取余数,那就在RemainderClass类里多写那几种方法.
using System;
class RemainderClass
{
public static void MethodRemainder(int num)
{
if (num % 2 == 0)
{
Console.WriteLine("这个整数取余数是0");
}
}
public static void MethodRemainderTwo(decimal num)
{
if (Math.Truncate(num) % 2 == 0)
{
Console.WriteLine("这个小数的整数部分取余数是0");
}
}
public static void MethodRemainderThree(int num)
{
if (Math.Pow(num, 3) % 2 == 0)
{
Console.WriteLine("这个整数^3之后取余数是0");
}
}
}
我又新加了第二种和第三种方法.第二种方法会把参数先取整数部分然后再除以2,Math.Truncate方法的作用是把传进来的decimal参数舍去小数部分然后取整数.第三种方法会把参数先进行三次幂然后在除以2.Math.Pow的作用是返回第一个参数的第二个参数次幂.
至此,判断用户输入的数字除以2余数是否为0这个小程序暂时告一段落.
好,若是未来我们要继续添加新的判断方法直接在RemainderClass.cs上下刀子就可以了.
但假如,我们有两个人来维护这个程序呢?现在你要把自己精分成两个人.一个人小A维护Program.cs,另一个人小B维护RemainderClass.cs.如果小A有了新的数据类型,例如double类,或者某个其他类里的数据需要进行取余数判断,他就要通知小B来添加新的方法来支持他的需求.小A他主要负责Program.cs,而且因为各种各样的原因不能去维护RemainderClass.cs,所以每次他有新的需求,他就必须去找小B,这样麻不麻烦?顺带一提,两者之间有互相依赖的关系,这个就称之为耦合
所以合理的方式是,每当小A有需求,他应该自己来编写逻辑来满足他的需求.小B只需要负责主要功能的实现.现在来审视代码,MethodRemainder的主要功能应该是接受数据和逻辑,输出结果.而小A需要编写逻辑,例如我这个数据究竟该怎么判断.
那么小A和小B的方法,把两者联系起来的关键是什么?答案就是委托.
现在来看加入委托之后代码变成了什么样子.
文件一:Program.cs
using System;
namespace CSharplearn
{
class Program
{
public delegate bool Remainder(int num);
static void Main(string[] args)
{
Remainder remainder = new Remainder(RemainderLogic);
RemainderClass.MethodRemainder(12, remainder);
}
public static bool RemainderLogic(int num)
{
return num % 2 == 0;
}
public static bool RemainderLogicThree(int num)
{
return Math.Pow(num, 3) % 2 == 0;
}
}
}
文件2:RemainderClass.cs
using System;
class RemainderClass
{
public static void MethodRemainder(int num,CSharplearn.Program.Remainder remainder)
{
if (remainder(num))
{
Console.WriteLine($"{num}取余数是0");
}
else
{
Console.WriteLine($"{num}取余数不是0");
}
}
}
(我少写了一个逻辑,后面会讲)
(CSharplearn.Program.Remainder可以用using static CSharplearn.Program简化成 Remainder,因为本文重点不在using上,怕新人读了会混乱,故采用这种方式)
我来一步步讲解这个程序是如何运行的.
首先定义了一个委托Remainder,返回值是bool,参数是int类型的.Main()方法里实例化了一个委托,传的方法是RemainderLogic.现在我们来看RemainderLogic,参数模2等于0的话就返回true.回到Main(),第二条语句调用了RemainderClass里的方法MethodRemainder.现在跳到Remainder类里来看这个MethodRemainder方法.他的两个参数一个是数据(int num),一个是逻辑(CSharplearn.Program.Remainder remainder).因为remainder里面已经有一个方法RemainderLogic了,所以执行remainder(),就相当于执行RemainderLogic().所以.if(remainder(num))现在就等价与if(参数%2==0).
调用MethodRemainder()的时候,传了一个要判断的数据12和已经包好逻辑的remainder.现在来看结果:
如果我把new Remainder(RemainderLogic)的RemainderLogic替换成RemainderLogicThree,MethodRemainder判断的逻辑就变成了RemainderLogicThree.
这样做,RemainderClass就封闭起来了,而且两个类关系从互相依赖到依赖委托.当两者关系中间出现了中间商的时候,就称之为解耦
泛型委托
细心的同学发现了,我少写了一个参数为decimal的逻辑.这是因为这个逻辑的参数与委托的参数不一致,导致该委托无法容下这个逻辑.那怎么办,难道要为了参数类型不同就要多出n倍工作量来支持不同类型的参数?
不用,委托也支持泛型.泛型简单的来说就是把类型也化作一个可以变化的东西,不用一开始就声明,只有在我实际声明他是什么类型的时候他才变成什么类型.
下面来看加入了泛型委托后的代码
文件一:Program.cs
using System;
namespace CSharplearn
{
class Program
{
public delegate bool Remainder<T>(T num);
static void Main(string[] args)
{
Remainder<decimal> remainder = new Remainder<decimal>(RemainderLogicTwo);
RemainderClass.MethodRemainder<decimal>(12.9M, remainder);
}
public static bool RemainderLogic(int num)
{
return num % 2 == 0;
}
public static bool RemainderLogicTwo(decimal num)
{
return Math.Truncate(num) % 2 == 0;
}
public static bool RemainderLogicThree(int num)
{
return Math.Pow(num, 3) % 2 == 0;
}
}
}
文件2:RemainderClass.cs
using System;
class RemainderClass
{
public static void MethodRemainder<T>(T num,CSharplearn.Program.Remainder<T> remainder)
{
if (remainder(num))
{
Console.WriteLine($"{num}取余数是0");
}
else
{
Console.WriteLine($"{num}取余数不是0");
}
}
}
声明泛型委托的格式是
作用域修饰符 delegate 返回值类型 委托名< T >(T 参数);
这个T可以变化成任何名称,不过在写泛型的时候大家习惯写成T.
所以该怎么用呢?首先我补上了缺失的那个逻辑.在实例化委托的时候把这个逻辑传进去.因为这个逻辑用的参数是decimal,所以<>的内容也要变成decimal.当然处理数据的核心方法也要升级为泛型方法,使用泛型方法和泛型委托差不多.
如果要把逻辑换成RemainderLogic,改类型只需要往<>里填int就行了.
结果:
由于泛型不是本文重点,所以讲的不是很详细.只是顺带说明委托也可以使用泛型.
委托的多播
多播委托,其实就是一个委托承载多个方法.
由于我的上面的例子不好讲多播委托,我们来看一个新例子
using System;
namespace CSharplearn
{
class Program
{
public delegate int Calculator(int a,int b);
static void Main(string[] args)
{
Calculator calculator = new Calculator(Sum);
calculator += new Calculator(Mul);
calculator(2, 5);
Console.WriteLine($"存储的数字大小是{ Save.NumberSaver}");
}
public static int Sum(int a, int b)
{
return Save.NumberSaver += a + b;
}
public static int Mul(int a, int b)
{
return Save.NumberSaver += a * b;
}
}
static class Save
{
public static int NumberSaver;
}
}
这不是最简单的案例,但是能说明多播委托的大部分事情.
首先在初次实例化的时候传了Sum这个方法不难理解.
但是在第二就有了+=,把这个式子展开来就是calculator = calculator + new Calculator(Mul),意思就是说在原有的基础上再往里面赛一个方法Mul.
最后调用这个委托,传递参数2和5.多播委托的执行顺序是第一个塞的方法先执行,于是2+5=7,7这个数值被塞进了Save.NumberSaver;后塞的方法后执行,2*5=10,10这个数值也被塞进了Save.NumberSaver.于是Save.NumberSaver应该有7+10=17
来看结果正不正确:
如果后来我想移除calculator里的Mul方法,那就calculator -= new Calculator(Mul);,-=是用来去除的.
有些博客写着多播委托返回值必须为void云云,否则就算有返回值也不知道返回到哪里,那么这个案例就完美的打了他们的脸.返回值需要有个地方储存,在这份案例里就是Save类里的NumberSaver.