目录
什么是委托
委托是一种存储函数引用的类型, 委托的声明类似于函数,不带函数体, 使用关键字delegate 做前辍。 后面才是 返回类型、函数名、参数表。
当我们定义了委托之后, 就可以声明该委托的变量,接着把这个变量初始化为与委托具有相同返回类型 和参数列表的函数。之后,我们就可以使用该委托变量调用这些函数,就像该变量是一个函数一样。
namespace HelloWorld_Console
{
delegate void MyDel(int value); //声明委托类型
class Program
{
void PrintLow(int value)
{
WriteLine($"PrintLow 函数的值为:{value}");
}
void PrintHigh(int value)
{
WriteLine($"PrintHigh 函数的值为:{value}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
Random rand = new Random(); //创建随机整数生成器对象
int randomValue = rand.Next(99);
//初始化委托变量,创建委托对象
MyDel del = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
del(randomValue); //调用委托
ReadKey();
}
}
}
- 在创建了委托对象之后,并且初始化委托变量, 只有在程序运行时才能确定该委托变量执行的到底是哪些函数。
- 在声明委托类型时,不需要在类内声明, 因为它是类型声明,所以我们一般在类外声明委托。
使用委托的步骤 (239P)
委托也是一种自定义的类型,可以通过以下步骤使用委托:
- 声明一个委托类型。 类似于函数,关键字delegate 做前辍,没有函数体。
- 使用该委托类型声明一个委托变量。
- 创建委托类型的对象,把它赋值给委托变量。新的委托对象包括指向某个方法的引用, 这个方法和第一步定义的签名和返回类型一致。
- 还可以选择为委托对象增加其它方法。 这些方法必须与第一步定义的委托类型有相同的函数原型。
- 最后你就可以像调用其他方法一样调用委托。当您调用委托时,它所包含的每个方法都会被执行。
可以将委托视为包含某些具有相同签名和返回类型的有序方法列表的对象:
- 方法的列表称为调用列表。
- 委托保存的方法可以是来自任何类或结构, 只要它们的函数原型相同。
- 调用列表中的方法可以是实例方法和静态方法。
- 当调用委托时,将执行其调用列表中的每个方法。
- 当使用关键字new 为委托分配内存, 此时创建的委托对象会把该方法放入委托的调用列表。(242页)
给委托赋值 (242P)
由于委托是引用类型, 我们可以通过给它赋值来使该委托变量重新指向一个新的委托对象。旧的委托对象会被垃圾回收器(GC)回收。
namespace HelloWorld_Console
{
delegate void MyDel(int value); //声明委托类型
class Program
{
void PrintLow(int value)
{
WriteLine($"PrintLow 函数的值为:{value}");
}
void PrintHigh(int value)
{
WriteLine($"PrintHigh 函数的值为:{value}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel mdel = myProgram.PrintLow; //委托变量首先 存储PrintLow 函数的引用
int printValue = 5;
mdel(printValue); //使用委托
mdel = myProgram.PrintHigh; //为委托赋值,该委托是一个全新的委托
mdel(printValue); //使用委托
ReadKey();
}
}
}
输出结果为:
PrintLow 函数的值为:5
PrintHigh 函数的值为:5
使用 + 运算符来组合委托 (243P)
委托可以使用+ 运算符来 “组合 ”, 操作的结果是创建一个新的委托, 其调用列表连接了作为操作数的两个委托的调用列表的副本。
namespace HelloWorld_Console
{
delegate void MyDel(int value); //声明委托类型
class Program
{
void PrintLow(int value)
{
WriteLine($"PrintLow 函数的值为:{value}");
}
void PrintHigh(int value)
{
WriteLine($"PrintHigh 函数的值为:{value}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel delA = myProgram.PrintLow;
MyDel delB = myProgram.PrintHigh;
MyDel delC = delA + delB; //组合调用列表,现在delC的调用列表有两个方法
Random rand = new Random(); //创建随机整数生成器对象
int randomValue = rand.Next(99);
//初始化委托变量,创建委托对象
delC = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
delC(randomValue); //调用委托
ReadKey();
}
}
}
注意: 委托是恒定的, 委托对象被创建后不能再被改变。
使用 += 运算符为委托添加方法 (243P)
为委托添加方法使用 += 运算符。 如下代码为委托添加了两个方法, 添加的方法会到调用列表中的底部。 在调用列表中的顺序是你依次添加的顺序。
namespace HelloWorld_Console
{
delegate void MyDel(int value); //声明委托类型
class Program
{
void PrintLow(int value)
{
WriteLine($"PrintLow 函数的值为:{value}");
}
void PrintHigh(int value)
{
WriteLine($"PrintHigh 函数的值为:{value}");
}
void Print(int value)
{
WriteLine($"Print 函数的值为:{value}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel delVar = myProgram.PrintLow; // 创建委托变量,并且创建委托对象
delVar += myProgram.PrintHigh; // 为该委托变量添加方法。现在该委托变量的调用列表有三个方法
delVar += myProgram.Print;
Random rand = new Random(); //创建随机整数生成器对象
int randomValue = rand.Next(99);
//初始化委托变量,创建委托对象
delVar = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
delVar(randomValue); //调用委托
ReadKey();
}
}
}
注意: 在使用+= 运算符时, 因为委托是不可变的,所以为委托的调用列表添加了两个方法后的结果其实是委托变量指向了一个新的委托。
运算符 -= 运算符为委托移除方法 (244P)
使用运算符 -= 从委托删除方法。与委托增加方法一样,当删除一个委托方法是,其实是创建了一个新的委托。 新的委托是旧委托的副本, 只是没有了已经被删除方法的引用。
namespace HelloWorld_Console
{
delegate void MyDel(int value); //声明委托类型
class Program
{
void PrintLow(int value)
{
WriteLine($"PrintLow 函数的值为:{value}");
}
void PrintHigh(int value)
{
WriteLine($"PrintHigh 函数的值为:{value}");
}
void Print(int value)
{
WriteLine($"Print 函数的值为:{value}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel delVar = myProgram.PrintLow; // 创建委托变量,并且创建委托对象
delVar += myProgram.PrintHigh; // 为该委托变量添加方法。现在该委托变量的调用列表有三个方法
delVar += myProgram.Print;
delVar-= myProgram.Print; //为委托删除一个方法, 现在委托变量的调用列表有两个方法
Random rand = new Random(); //创建随机整数生成器对象
int randomValue = rand.Next(99);
//初始化委托变量,创建委托对象
delVar = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
delVar(randomValue); //调用委托
ReadKey();
}
}
}
如下是移除委托方法时应注意的有:
- 如果在调用列表中该委托有多个方法, - = 运算符从列表的底部开始搜索, 并且移除第一个与委托相匹配(指的是原型匹配)的方法。
- 试图删除不在调用列表中的方法没有效果。
- 试图调用空委托(说白了就是该委托变量没有初始化为一个委托对象)会抛出异常。 我们可以通过把委托和null 进行比较来判断委托的调用列表是否为空。 如果调用列表为空, 则委托是null。
调用委托
可以像调用方法一样简单地调用委托。 用于调用委托的参数将会用于调用调用列表中的每一个方法(除非有输出参数)。
namespace HelloWorld_Console
{
delegate void PrintFunction(); //声明委托类型
class Program
{
public void Print1()
{
WriteLine("调用 Print1 函数.");
}
public static void Print2()
{
WriteLine("调用 Print2 函数.");
}
static void Main(string[] args)
{
Program myProgram = new Program();
PrintFunction pf = myProgram.Print1; //实例化并且初始化该委托
//给委托添加3个另外的方法
pf += Program.Print2;
pf += myProgram.Print1;
pf += Print2;
// 现在,委托含有4个方法
if (pf != null)
{
pf();
}
else
WriteLine("该委托是空的!");
ReadKey();
}
}
}
输出结果为:
调用 Print1 函数.
调用 Print2 函数.
调用 Print1 函数.
调用 Print2 函数.
- 注意 :如果一个方法在调用列表中出现多次,当委托被调用时, 每次在列表中遇到这个方法时它都会被调用一次。
- 注意: 如果初始化委托变量的 函数是静态的, 那么必须是 “ 类名. 成员函数名 ” 或者 直接在赋值运算符后面写 某个具体的函数名 。 如上面的 函数 Print2();
调用带返回值的委托 (246P)
如果委托有返回值并且在调用列表中有一个以上的方法,会发生下面的情况:
- 调用列表中最后一个方法返回的值就是委托调用返回的值。那么输出的值就是最后一个返回值的值。
- 调用列表中所有其它方法的返回值都会被忽略。
namespace HelloWorld_Console
{
delegate int MyDel(); //声明委托类型
class Program
{
int IntValue = 5;
public int Add2() => IntValue += 2;
public int Add3() => IntValue += 3;
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel mDel = myProgram.Add2; //创建并初始化委托
mDel += myProgram.Add3; // 为委托添加方法
mDel += myProgram.Add2;
if (mDel != null)
{
WriteLine($"输出值为:{mDel()}"); //调用委托并返回值
}
else
WriteLine("该委托是空的!");
ReadKey();
}
}
}
输出结果为:
输出值为:12
调用带引用(ref)参数的委托 (247)
如果委托有引用参数, 形参的值会根据调用列表中的一个或多个方法的返回值而改变。
意识就是说,在调用委托列表中的下一个方法时, 上一个方法形参的值不管是否改变,都会传递给调用列表中下一个方法, 以此类推。
namespace HelloWorld_Console
{
delegate void MyDel (ref int X); //声明委托类型
class Program
{
public void Print1(ref int x) => x += 2;
public void Print2(ref int x) => x += 3;
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel mDel = myProgram.Print1; //创建并初始化委托
mDel += myProgram.Print2; // 为委托添加方法
mDel += myProgram.Print1;
int x = 5;
mDel(ref x);
if (mDel!=null)
{
WriteLine($"输出委托最后的返回值:{x}");
}
ReadKey();
}
}
}
输出结果为:
输出委托最后的返回值:12
匿名方法
我们可以使用静态方法或实例方法来初始化委托并且创建委托对象。对于这种情况, 这些方法本身可以被代码的其他部分显式调用。不过,这个部分也必须是某个类或结构的成员。
然而,如果方法只会被使用一次——用来初始化委托会怎么样呢?在这种情况下,除了创建委托的语法需要,没有必要创建独立的具名方法。匿名方法允许我们避免使用独立的具名方法。
- 说明 : 匿名方法是在初始化委托时内联( inline )声明的方法。
使用匿名方法 (249P)
我们可以在如下地方使用匿名方法:
- 声明委托变量时作为初始化表达式
- 在组合委托时位于赋值语句的右侧。
- 为委托添加事件时在赋值语句的右边。
匿名方法的语法
使用匿名方法的语法为:
delete (Paramters){ // Implementation code}
- 其中 Paramters 是参数列表, 如果没有任何想要使用的参数,可以省略。
- { } 里是语句块, 它包含了匿名方法的代码。
下面的代码显式初始化委托:
namespace HelloWorld_Console
{
delegate int MyDel ( int X); //声明委托类型
class Program
{
public int Print1(int x) => x+20 ;
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel mDel = myProgram. Print1; //创建并初始化委托
WriteLine($"输出值为:{mDel(5)}");
ReadKey();
}
}
}
输出值为:25
下面的代码隐式初始化委托:
namespace HelloWorld_Console
{
delegate int MyDel ( int X); //声明委托类型, 该类型返回一个int
class Program
{
//public int Print1(int x) => x+20 ;
static void Main(string[] args)
{
Program myProgram = new Program();
MyDel mDel = delegate (int x)
{
return x + 20; // 匿名方法的实现代码本身的返回值类型 必须跟委托的返回类型相同,它们都是int
};
WriteLine($"输出值为:{mDel(20)}");
ReadKey();
}
}
}
输出结果为: 40
- 注意: 匿名方法不需要显式地声明返回类型, 然而, 匿名方法的实现代码本身的 返回值类型 必须跟委托的返回类型 相同。
- 比如:如果委托是void 类型的返回值, 匿名方法就没有返回值。
注意: 除了数组参数,匿名方法的参数列表必须跟委托的 : 参数数量、参数类型、顺序、修饰符都要一样。 说白了就是它们的原型要一样。
注意: 可以使用 圆括号为空 或者 省略圆括号来 简化 匿名方法的参数列表, 但是必须满足以下两个条件:
- 委托的参数列表不包含任何 out 参数。
- 匿名方法不使用任何参数。
注意: 如果委托声明的参数列表包含了 params 参数, 那么匿名方法的参数列表 将忽略 params 关键字的前辍。
关于 params 关键字的 详细内容,点击 这里 查看。
Lambda 表达式 (252P)
我们可以使用下列步骤来把 匿名方法 转换为 Lambda 表达式:
- 首先删除 delegate 关键字
- 在形参列表和匿名方法的函数体之间方 Lambda 运算符 =>
如下代码演示了这种转换。第一行演示了将匿名方法赋值给变量mDel。第二行演示了同样的 ,匿名方法在被转换成Lambda表达式之后,赋值给了变量del:
MyDel mDel = delegate (int x) { return x + 20; }; // 匿名方法
MyDel del = (int x) => { return x + 20; }; // Lambda 表达式
不过我们还可以更简洁,编译器可以通过推断从委托的声明中知道委托形参数类型, 因此 Lambad 表达式允许我们省略委托形参数类型。 如下面代码中 le2 的赋值代码所示:
记住:
- 带有委托形参数类型的列表称为显式类型
- 省略委托形参数类型的列表称为隐式类型
MyDel de1 = delegate (int x) { return x + 1; }; // 匿名方法
// 都是 Lambda 表达式
MyDel le1 = (int x) => { return x + 1; }; // 显式类型
MyDel le2= (x) => { return x + 1; }; // 隐式类型
MyDel le3= x => { return x + 1; };
MyDel le4 = x => x + 1;
- 注意 : 如果圆括号中只有一个隐式类型参数, 我们可以省略周围的圆括号, 如 le3 的赋值代码所示。
- 注意: 最后,lambda表达式允许表达式的主体可以是语句块,也可以是表达式。如果语句块只包含一个返回语句,则可以使用return关键字后面的表达式来替换语句块,如le4的赋值所示。
namespace HelloWorld_Console
{
delegate int MyDel(int par); //声明委托类型
class Program
{
static void Main(string[] args)
{
MyDel de1 = delegate (int x) { return x + 1; }; // 匿名方法
// 都是 Lambda 表达式
MyDel le1 = (int x) => { return x + 1; };
MyDel le2 = (x) => { return x + 1; };
MyDel le3 = x => { return x + 1; };
MyDel le4 = x => x + 1;
WriteLine($"分别输出值为:{de1(20)},{le1(20)},{le2(20)}" +
$",{le3(20)},{le4(20)}");
ReadKey();
}
}
}
输出结果为:
分别输出值为:21,21,21,21,21
有关Lambda 表达式的参数列表的要点如下:
- Lambda 表达式参数列表中的参数必须在 参数个数、类型、顺序与委托匹配。
- 表达式的参数列表中的参数不一定需要 包含类型(此时是隐式类型), 如果委托有 ref 或out 参数——此时必须注明显式类型.
- 如果只有一个参数, 并且是隐式类型的, 周围的圆括号可以被省略, 否则必须有括号。
- 如果没有参数, 必须使用一组空的圆括号。