C# 委托:分步讲解






4.68/5 (30投票s)
一篇帮助初学者理解委托的文章。
引言
初次接触委托时,可能会觉得难以理解,因为委托的使用方式与传统的调用方法的方式有所不同。在传统的调用方法时,你会创建一个包含你想要调用的方法的对象,然后调用该方法,传入所需的参数。整个过程一次性完成,包括定义要调用哪个对象和哪个方法,以及执行调用。而通过委托进行的方法调用则采取了不同的方法。它将方法调用的定义与方法的实际调用分成了两部分。委托是 .NET 中方法的地址。它们是类型安全的类,定义了返回类型和参数类型。委托类不仅包含对一个方法的引用,还包含对多个方法的引用。尽管委托自 .NET 2.0 以来就已存在,但它们在今天的 .NET 4 中仍然扮演着重要角色。Lambda 表达式与委托直接相关。当参数是委托类型时,你可以使用 Lambda 表达式来实现一个由委托引用的方法。委托适用于将方法作为参数传递给其他方法的场景。为了理解这一点,请看以下代码
using System;
public class Test
{
public delegate int CalculationHandler(int x, int y);
static void Main(string[] args)
{
Math math = new Math();
//create a new instance of the delegate class
CalculationHandler sumHandler = new CalculationHandler(math.Sum);
//invoke the delegate
int result = sumHandler(8,9);
Console.WriteLine("Result is: " + result);
}
}
public class Math
{
public int Sum(int x, int y)
{
return x + y;
}
}
结果是:17
委托中封装的关于方法的信息可以分为两类:方法签名和方法目标。方法签名包括有关方法参数的数量和类型以及返回类型的信息。方法目标包括方法的名称以及方法所属的对象。当我们创建一个封装了方法调用的委托对象时,必须提供这两组信息。
在程序中使用委托的第一步是定义一个委托类,指定委托类能够处理的方法的签名。在我们的例子中,我们定义了一个名为 CalculationHandler
的委托类,它可以处理一个有两个整数参数和一个整数返回值的方法。如果方法没有任何返回值,那么我们必须使用“void
”代替。在定义了 CalculationHandler
之后,它就成为现有类(在本例中为 Test
类)的内部类。
定义了委托类之后,下一步是创建该类的实例并将其绑定到特定的目标。需要注意的是,首先,委托类只有一个构造函数,该构造函数接受方法目标作为其唯一参数。这会将委托对象绑定到一个物理目标。其次,构造函数中指定的方法目标必须与委托类中定义的方法签名相匹配。也就是说,我们必须确保 Sum
方法与 CalculationHandler
的定义相符,后者规定目标方法必须接受两个整数参数并返回一个整数。
在下一个示例中,我们实例化一个 GetAString
类型的委托,然后对其进行初始化,使其指向整数变量 x
的 ToString()
方法。C# 中的委托始终在语法上接受一个参数的构造函数,该参数是要委托引用的方法。此方法必须与你最初定义的委托签名匹配。因此,在这种情况下,如果我们尝试使用任何不接受参数且返回 string
的方法来初始化变量 firstStringMethod
,将会导致编译错误。请注意,由于 int.ToString()
是一个实例方法(而不是 static
方法),我们需要同时指定实例(x
)和方法名才能正确初始化委托。下一行实际上使用了委托来显示 string
。在任何代码中,提供委托实例的名称,后跟包含任何参数的方括号,其效果与调用委托封装的方法完全相同。
using System;
public class Program
{
public delegate string GetAString();
public static void Main()
{
int x = 40;
GetAString firstStringMethod = x.ToString;
Console.WriteLine("String is {0}", firstStringMethod());
Currency balance = new Currency(34, 50);
// firstStringMethod references an instance method
firstStringMethod = balance.ToString;
Console.WriteLine("String is {0}", firstStringMethod());
// firstStringMethod references a static method
firstStringMethod = new GetAString(Currency.GetCurrencyUnit);
Console.WriteLine("String is {0}", firstStringMethod());
}
}
您可以看到有一个 Currency
类,它创建一个对象
using System;
public class Currency {
public uint Dollars;
public ushort Cents;
public Currency(uint dollars, ushort cents)
{
this.Dollars = dollars;
this.Cents = cents;
}
public override string ToString()
{
return string.Format("${0}.{1,-2:00}", Dollars, Cents);
}
public static string GetCurrencyUnit()
{
return "Dollar";
}
public static explicit operator Currency(float value)
{
checked
{
uint dollars = (uint)value;
ushort cents = (ushort)((value - dollars) * 100);
return new Currency(dollars, cents);
}
}
public static implicit operator float(Currency value)
{
return value.Dollars + (value.Cents / 100.0f);
}
public static implicit operator Currency(uint value)
{
return new Currency(value, 0);
}
public static implicit operator uint(Currency value)
{
return value.Dollars;
}
}
csc.exe /t:library Currency.cs
csc.exe /r:Currency.dll Program.cs
String is 40
String is $34.50
String is Dollar
这个例子定义了一个 MathOperations
类,其中包含几个 static
方法来对双精度数执行两个操作。然后,我们使用委托来调用这些方法。数学类如下所示
using System;
public class MathOperations
{
public static double MultiplyByTwo(double value)
{
return value * 2;
}
public static double Square(double value)
{
return value * value;
}
}
我们这样调用这些方法
using System;
delegate double DoubleOp(double x);
class Application
{
static void Main()
{
DoubleOp[] operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
};
for (int i = 0; i < operations.Length; i++)
{
Console.WriteLine("Using operations[{0}]:", i);
ProcessAndDisplayNumber(operations[i], 2.0);
ProcessAndDisplayNumber(operations[i], 7.94);
ProcessAndDisplayNumber(operations[i], 1.414);
Console.WriteLine();
}
}
static void ProcessAndDisplayNumber(DoubleOp action, double value)
{
double result = action(value);
Console.WriteLine(
"Value is {0}, result of operation is {1}", value, result);
}
}
输出
Using operations[0]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 15.88
Value is 1.414, result of operation is 2.828
Using operations[1]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 63.0436
Value is 1.414, result of operation is 1.999396
在这段代码中,我们实例化了一个 DoubleOp
委托的数组(请记住,在定义了委托类之后,你基本上可以像实例化普通类一样实例化它,所以将一些委托放入数组中没问题)。数组的每个元素都被初始化为引用 MathOperations
类实现的某个不同操作。然后,我们遍历数组,将每个操作应用于三个不同的值。这说明了委托的一种用法——你可以使用委托将方法分组到数组中,以便在循环中调用多个方法。这段代码的关键行是实际将每个委托传递给 ProcessAndDisplayNumber()
方法的那些行,例如
ProcessAndDisplayNumber(operations[i], 2.0);
下面是一个应该能阐明委托含义的例子。注意每个实例的两种信息类别。正如我们所见,这种对象之间的间接引用促进了“解耦”。在客户端和服务器之间放置委托或方法对象,可以让我们在不调用它们的情况下定义某些操作。这在使用多个应用程序完成任务时可能很有优势。我们希望每个应用程序对其他应用程序的了解很少;因此,如果一个应用程序的行为发生变化,例如提供服务的应用程序,那么使用服务的应用程序将不会改变其行为。如果服务提供者突然改变其操作流程,服务消费者也不会改变其操作程序。然而,下面的例子旨在坚持委托的通用原则
using System;
class Program
{
/// Delegate declaration.
delegate string UppercaseDelegate(string input);
/// Delegate implementation 1.
static string UppercaseFirst(string input)
{
char[] buffer = input.ToCharArray();
buffer[0] = char.ToUpper(buffer[0]);
return new string(buffer);
}
/// <summary>
/// Delegate implementation 2.
/// </summary>
static string UppercaseLast(string input)
{
char[] buffer = input.ToCharArray();
buffer[buffer.Length - 1] = char.ToUpper(buffer[buffer.Length - 1]);
return new string(buffer);
}
/// Delegate implementation 3.
static string UppercaseAll(string input)
{
return input.ToUpper();
}
/// <summary>
/// Receives delegate type.
/// </summary>
static void WriteOutput(string input, UppercaseDelegate del)
{
Console.WriteLine("Your string before: {0}", input);
Console.WriteLine("Your string after: {0}", del(input));
}
static void Main()
{
// Wrap the methods inside delegate instances and pass to the method.
WriteOutput("gems", new UppercaseDelegate(UppercaseFirst));
WriteOutput("gems", new UppercaseDelegate(UppercaseLast));
WriteOutput("gems", new UppercaseDelegate(UppercaseAll));
}
}
产生
Your string before: gems
Your string after: Gems
Your string before: gems
Your string after: gemS
Your string before: gems
Your string after: GEMS
评估
要使用委托,你需要定义一个或多个处理程序方法,在实例化委托时分配处理程序,并对委托实例执行调用。下面是一个恰当的例子
using System;
// 1. Define delegate.
public delegate double UnitConversion(double from);
class QuickDelegateDemo
{
// 2. Define handler method.
public static double FeetToInches(double feet)
{
return feet * 12;
}
static void Main()
{
// 3. Create delegate instance.
UnitConversion doConversion = new UnitConversion(FeetToInches);
Console.WriteLine("Feet to Inches Converter");
Console.WriteLine("------------------------\n");
Console.Write("Please enter feet: ");
double feet = Double.Parse(Console.ReadLine());
// 4. Use delegate just like a method.
double inches = doConversion(feet);
Console.WriteLine("\n{0} Feet = {1} Inches.\n", feet, inches);
Console.ReadLine();
}
}
输出
Feet to Inches Converter
------------------------
Please enter feet: 12
12 Feet = 144 Inches.
当我们使用委托来封装方法调用时,我们的思维方式必须从传统的看待方法调用的方式转变为面向对象的观点。异步编程产生了功能强大的应用程序,这得益于委托。类似于排长队购物,无所事事,也许可以类比为在你前面的人完成任务时,你也同时执行一项任务。
历史
- 2010年9月11日:首次发布