C#中的委托详解
如果要给方法传递一个方法参数时,就可以使用委托。要传递方法,就必须把方法的细节封装在一钟新类型的对象中,即委托。委托是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托只包含一个或多个方法的地址。
.NET版本中,委托指向方法的地址。在C++中,函数指针是一个指向内存位置的指针,但它不是类型安全的。开发者无法判断这个指针实际指向什么,像参数和返回值等项就更不知道了。
.NET委托是类型安全的类,它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。
1.声明委托
使用委托和使用类一样,也需要经过定义和实例化两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法。然后,必须创建该委托的一个或多个实例才能使用。编译器在后台将创建表示该委托的一个类。
定义委托的语法:
delegate void IntMethod(int x);
//定义了一个委托IntMethod,指定该委托的每个实例都可以包含一个或多个方法的引用,引用的方法必须带有一个int参数,并返回void.
因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托。也可以在委托的定义上使用修饰符:public,private,protected等。
委托派生自基类System.MulticastDelegate,MulticastDelegate又派生自基类System.Delegate.
类有两个不同的术语:“类”表示广义的定义,“对象”表示;类的实例。但委托只有一个术语。在创建委托的实例时,所创建的实例仍称为委托。必须从上下文中确定委托的具体含义。
2.使用委托
定义好委托之后,就可以创建它的一个实例,从而用它存储特定方法的细节。
delegate void IntMethod(int x);
static void Fun(int x)
{
Console.WriteLine(x);
}
static void Main()
{
int x = 40;
IntMethod intMethod = new IntMethod(Fun);
intMethod(x);
Console.ReadKey();
}
委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。
使用委托实例的名称,后面加上圆括号,如果需要参数就必须在圆括号内加上参数。
给委托实例提供圆括号和调用委托类的Invoke()方法完全相同:
intMethod(x);
intMethod.Invoke(x);
为了减少输入量,只需要给委托实例传递方法地址的名称就可以,这称为委托推断。
IntMethod intMethod = new IntMethod(Fun);
IntMethod intMethod =Fun;
委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托。(事件后面文章有介绍)
注意,使用委托可以调用任何类型对象的方法,不管是静态方法还是实例方法。
3.使用委托数组
//先在一个类中定义两个方法:
class MathOperations
{
public static double MultiplyByTwo(double value)
{
return value * 2;
}
public static double Square(double value)
{
return value * value;
}
}
//定义一个返回double类型且带有double类型参数的委托
delegate double DoubleOp(double x);
class Program
{
static void Main()
{
//实例化委托数组,和实例化类的数组一样
DoubleOp[] operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
};
//遍历数组,使用数组中的每个委托实例
for (int i = 0; i < operations.Length; i++)
{
Console.WriteLine("Using operations[{0}]:", i);
//将委托实例作为参数传递给ProcessAndDisplayNumber方法
ProcessAndDisplayNumber(operations[i], 2.0);
ProcessAndDisplayNumber(operations[i], 7.94);
ProcessAndDisplayNumber(operations[i], 1.414);
Console.WriteLine();
}
}
static void ProcessAndDisplayNumber(DoubleOp action, double value)
{
//在ProcessAndDisplayNumber中调用委托,执行委托实例引用的方法
double result = action(value);
Console.WriteLine(
"Value is {0}, result of operation is {1}", value, result);
}
}
4.Action
除了为每个参数和返回类型定义一个委托类型之外,还可以使用Action
泛型Action
Func
下面使用Func
定义包含比较方法的类:
BubbleSorter类实现了一个泛型方法 Sort
class BubbleSorter
{
static public void Sort
{
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Count - 1; i++)
{
//调用委托中引用的方法,比较两个对象
if (comparison(sortArray[i + 1], sortArray[i]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
}
定义自定义的一个类
class Employee
{
public Employee(string name, decimal salary)
{
this.Name = name;
this.Salary = salary;
}
public string Name { get; private set; }
public decimal Salary { get; private set; }
public override string ToString()
{
return string.Format("{0}, {1:C}", Name, Salary);
}
public static bool CompareSalary(Employee e1, Employee e2)
{
return e1.Salary < e2.Salary;
}
}
客户端代码:
Employee[] employees =
{
new Employee("Bugs Bunny", 20000),
new Employee("Elmer Fudd", 10000),
new Employee("Daffy Duck", 25000),
new Employee("Wile Coyote", 1000000.38m),
new Employee("Foghorn Leghorn", 23000),
new Employee("RoadRunner", 50000)
};
//Sort执行了自定义的Employee.CompareSalary方法
BubbleSorter.Sort(employees, Employee.CompareSalary);
foreach (var employee in employees)
{
Console.WriteLine(employee);
}
5.多播委托
前面介绍的每个委托只包含一个方法的调用,委托也可以包含多个方法。这种委托称为多播委托。
如果调用多播委托,就可以按顺序调用多个方法,但如果委托的签名不是返回void,就只能得到委托调用的最后一个方法的结果。
使用+=添加方法,-=删除方法。
static void Main()
{
Action
operations += MathOperations.Square;
ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.0);
Console.WriteLine();
}
static void ProcessAndDisplayNumber(Action
{
Console.WriteLine();
Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
action(value);
}
class MathOperations
{
public static void MultiplyByTwo(double value)
{
double result = value * 2;
Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
}
public static void Square(double value)
{
double result = value * value;
Console.WriteLine("Squaring: {0} gives {1}", value, result);
}
}
每次调用ProcessAndDisplayNumber方法,都会按顺序调用action委托实例中的两个方法。
输出:
ProcessAndDisplayNumber called with value = 2
Multiplying by 2: 2 gives 4
Squaring: 2 gives 4
ProcessAndDisplayNumber called with value = 7
Multiplying by 2: 7 gives 14
Squaring: 7 gives 49
委托还可以使用+,-运算符:
Action
Action
Action
operations = operations - operations2;
多播委托包含一个逐个调用的委托集合。如果其中一个方法抛出异常,整个迭代就会停止。
static void One()
{
Console.WriteLine("One");
throw new Exception("Error in one");
}
static void Two()
{
Console.WriteLine("Two");
}
static void Main()
{
Action d1 = One;
d1 += Two;
try
{
d1();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
委托只调用了第一个方法。因为第一个方法抛出异常,委托的迭代停止,不再调用Two()方法。
避免这个问题,可以使用Delegate类定义的GetInvocationList()方法,它返回一个Delegate对象数组:
Action d1 = One;
d1 += Two;
Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
try
{
d();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
输出:
One
Exception caught
Two
使用GetInvocationList()方法可以为委托的每个方法传递不同的参数,获取每个方法的返回值。
static int One(int x)
{
return x;
}
static int Two(int x)
{
return x;
}
static void Main()
{
Func
d1 += Two;
Delegate[] delegates = d1.GetInvocationList();
Func
Console.WriteLine( d2(1));
Func
Console.WriteLine(d3(2));
Console.ReadKey();
}
输出:
1
2
6.匿名方法
使用匿名方法可以将方法体直接赋给委托实例,而不需要定义一个方法。
static void Main()
{
string mid = ", middle part,";
Func
{
param += mid;
param += " and this was added to the string.";
return param;
};
Console.WriteLine(anonDel("Start of string"));
}
上面代码不是把方法名赋给委托变量anonDel,而是一段代码,它前面是关键字delegate和参数列表。在使用匿名方法时,可以使用外部变量。
匿名方法的优点是减少了代码量。使用匿名方法,代码执行速度并没有加快。编译器仍定义了一个方法,该方法只有一个自动指定的名称。
使用匿名方法,必须遵守两条规则:
如果需要匿名方法多次编写同一个功能时,就不要用匿名方法了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
您可能感兴趣的文章:
- .NET Core系列之MemoryCache 初识
- 007手机一键Root(安机网一键Root) v3.0 官方最新版 一键ROOT您的Android手机
- 12306密码被盗了怎么办?12306密码外泄解决方法
- 12个字的qq网名
- 150M迷你型无线路由器怎么设置?
- 192.168.1.1打不开怎么办?路由器192.168.1.1打不开的原因以及解决办法
- 2011年电子报合订本 电子报 编辑部 中文 PDF版 [84M]
- 2015年1月15日小米新旗舰发布会现场图文直播
- 2016.3.1vivo Xplay5新品发布会现场视频直播 优酷直播
- 2016华为P9发布会视频直播地址 4月15日华为P9国行发布会直播