前言:
我是一个C++的铁杆粉丝,但是现在还是一个正在学习C++ 的小白。因为毕业后想搞游戏开发,所用的引擎是Unity,所以不得不好好学习C#。我在从C++ 转而学习C#时,遇到的第一个问题就是C# 中的委托和事件。(我认为C# 的委托特别像C++ 中的函数指针,但是比函数指针要强大)。本篇博文我将会详细讲解什么是C# 的委托,以及关于C# 委托的相关常用操作方法。
C#委托的引入:
在中国的传统教育中,考试成绩被认为是可以判断一个学生的优秀程度。现在初中生小明回到家里,家里人问他数学考了多少(可怜的小明刚好及格),于是小明告诉了家里数学考了多少分:
public static void SayMyScores(int number)
{
MathScore(number);
}
public static void MathScore(int number)
{
Console.WriteLine("Math:" + number);
}
static void Main(string[] args)
{
Program.SayMyScores(60);
}
然后家里人问他语文考了多少,那么针对上个程序的问题来了:
我们能做的是再写一个函数,然后修改SayScores函数:(不幸运的是小明的语文也是60分)
public static void SayMyScores(int number)
{
LanguageScore(number);
}
public static void MathScore(int number)
{
Console.WriteLine("Math:" + number);
}
public static void LanguageScore(int number)
{
Console.WriteLine("Language:" + number);
}
static void Main(string[] args)
{
Program.SayMyScores(60);
}
enum Sbuject
{
Langguage,
Math
}
public static void SayMyScores(int number,Sbuject sbu)
{
if (sbu == Sbuject.Langguage)
LanguageScore(number);
else if (sbu == Sbuject.Math)
MathScore(number);
}
但是这种方式是绝对不可取的。
原因有很多:比如:如果当学科较多时,你的 if -else 或者是 switch要写多长?这种方式很不利于扩展和代码的维护。
那么我们应该采取怎样的方式呢?
或者你可以这样考虑:如果我们在函数中,让第二个代表一种参数变量,这种变量,当将MathScore赋值给次变量时,那么这个变量就代表了MathScore方法;当将LanguageScore赋值给该参数变量时,该变量代表的就是LanguageScore方法。那么当我们执行该参数变量时,就是在执行MathScore或者LanguageScore方法。
我们来观察一下这两个有些类似的函数。首先返回类型的是:void ,参数是int和枚举类型。我们先看这里:
int ans = 0;
int num1 = 1;
int num2 = 2;
如果我们让ans = num1,那么ans 就是1,如果让ans = num2,那么ans 就是2。从这里,我们可以想到一种方式:我们用X(没错,就是字母X ) 类比ans ,num1 和num2 分别类比MathScore和LanguageScore方法。如果X = MathScore() 函数(MathScore 函数类比为num 1 ),那么X 就表示MathScore函数;如果X = LanguageScore() 函数,那么X 就表示是LanguageScore函数了。那么我们如果执行了X,最后得到的结果就是我们想要执行的函数所显示的结果了。
那么此时这个X 就可以称作是:::一个委托!!!。(理解最重要,博主在这里用了点技巧,意思理解到位就行)。
那么我们根据上面的案例接着做:
首先我们需要一个委托,用于处理某个科目的函数或者多个科目函数的调用。
public delegate void SubScore(int mumber);
然后将此委托类型作为
SayMyScores函数的参数,最后执行即可。我们看完整的代码:
namespace CSharpProject
{
public delegate void SubScore(int mumber);
class Program
{
private static void SayMyScores(int number,SubScore subScore)
{
subScore(number);
}
public static void MathScore(int number)
{
Console.WriteLine("Math:" + number);
}
public static void LanguageScore(int number)
{
Console.WriteLine("Language:" + number);
}
static void Main(string[] args)
{
SayMyScores(60, MathScore);
Console.ReadKey();
}
}
}
输出为:
Math:60
现在你已经对委托有了最简单最基础的了解了。那么我们在此总结下:
总结:
委托作为C#中的一个非常重要的概念是必须要掌握的。那么委托到底是什么呢?从数据结构层次来说,委托可以看做是一种用户自定义的类型。这种类型是专门处理具有相同签名和返回类型的。从设计的层次来讲,委托提供了方法的抽象。学过面向对象的应该可以理解这段话。
我认为,委托为一种类型,它是定义方法的类型,这样使得方法可以作为参数来进行传递。这种方式,很好的避免了代码中判断过多而且这种方式具有很好的扩展性。
那么委托既然可以作为一种类型,那么这种类型到底存储的是什么呢?简单理解:存储了具有相同签名和返回类型的方法的地址(跟C++的函数指针很是相似)。
ok,委托的概念已经将清楚了,下面就说下
委托的相关操作:
1.定义委托类型:
delegate void SubScore(int mumber);
委托类型声明:
delegate类型(公有|私有|保护) + delegate + 返回类型 + 函数名(函数参数);
2.声明委托变量:
SubScore sub;
这里:委托既然可以看做是一种类型,那么我们就可以根据类变量的声明来进行,跟:int number 类似。
3.初始化委托变量:
初始化委托变量常用的有两种方式:
1)通过new 运算符:
SubScore sub;
sub = new SubScore(Program.MathScore);
2)通过直接赋值:
SubScore sub;
sub = new SubScore(Program.MathScore);
sub = Program.LanguageScore;
4. 赋值委托:
需要注意的一点是委托是一种引用类型,那么我们可以直接将方法赋给它,然后如果你赋了很多方法,那么它只会引用最后赋值的方法,而对于其他的方法会被回收器回收:
static void Main(string[] args)
{
SubScore sub;
sub = new SubScore(Program.MathScore);
sub = Program.LanguageScore;
sub(60);
Console.ReadKey();
}
输出为:
Math:60
5.组合委托:
委托可以使用额外的运算符来组合。这个运算最终会创建一个新的委托,其调用列表是两个操作数的委托调用列表的副本的连接。委托是恒定的,操作数委托创建后不会被改变。委托组合拷贝的是操作数的副本。
SubScore sub1;
SubScore sub2;
SubScore sub3;
sub1 = new SubScore(Program.MathScore);
sub2 = Program.LanguageScore;
sub3 = sub1 + sub2;
sub3(61);
输出为:
Math:60
Language:60
6.委托的加减操作:
委托有时候可以看作是一种方法的数组,这样,我们就可以往数组里添加函数或者是删除函数:利用加法可以让一个委托变量指向多个方法,那么当我们调用时,会顺序的调用委托所引用的所有方法
利用减法何以在指向的多个方法中删除某种方法:
static void Main(string[] args)
{
SubScore sub1;
sub1 = new SubScore(Program.MathScore);
sub1 += Program.LanguageScore;
sub1(61);
Console.ReadKey();
}
此时输出:
Math:61
Language:60
此时我们删除MathScore方法:
static void Main(string[] args)
{
SubScore sub1;
sub1 = new SubScore(Program.MathScore);
sub1 += Program.LanguageScore;
sub1(61);
Console.WriteLine("删除后输出内容:");
sub1 -= MathScore;
sub1(60);
Console.ReadKey();
}
此时输出:
Math:61
Language:61
删除后输出内容:
Language:60
7.委托调用:
委托的调用和方法很是类似,委托调用后,调用列表中的每个方法都会被执行。但是在调用委托之前,应该判断下委托是否为空,如果委托为空的话会抛出异常。 class Program
{
public delegate void SubScore(int number);
public static void SayMyScores(int number,SubScore sub)
{
sub(number);
}
public static void MathScore(int number)
{
Console.WriteLine("Math:" + number);
}
public static void LanguageScore(int number)
{
Console.WriteLine("Language:" + number);
}
static void Main(string[] args)
{
SubScore sub = MathScore;
sub += LanguageScore;
if(null != sub)
{
sub(60);
}
}
}
}
9.匿名方法:
匿名方法经常跟Lamabda结合使用,首先我简单先说下匿名方法(我会在最近更新一篇关于C#匿名方法的文章):
一般来说:在初始化委托时,可以内联一个方法,这个方法就是匿名方法:
如:
class Program
{
public delegate int Score(int number);
//public delegate void SubScore(int number);
//public static void SayMyScores(int number,SubScore sub)
//{
// sub(number);
//}
//public static void MathScore(int number)
//{
// Console.WriteLine("Math:" + number);
//}
//public static void LanguageScore(int number)
//{
// Console.WriteLine("Language:" + number);
//}
static void Main(string[] args)
{
Score myScore = delegate(int x)
{
return x;
};
Console.WriteLine(myScore(1));
}
}
}
输出为:
1
而当myScore 引用了其他的方法后,会发生什么呢?
这里我们实验一发:
class Program
{
public delegate int Score(int number);
public static int Say(int number)
{
Console.WriteLine("1111");
return 0;
}
static void Main(string[] args)
{
Score myScore = delegate(int x)
{
return x;
};
myScore = Program.Say;
Console.WriteLine(myScore(1));
}
}
输出为:
1111
0
看到这里就明白:会先调用引用的方法,然后调用本身的匿名方法。
关于委托还有很多重要的内容,这里就不一一列举了。等自己用熟练了,有了自己的深刻理解之后再分享给大家。