65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的委托 -尝试深入了解 -第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (61投票s)

2010年9月28日

CPOL

8分钟阅读

viewsIcon

127598

如何理解和在程序中使用委托

引言

许多程序员在开发中试图回避委托。我们都知道,我们可以使用熟悉的开发工具和方法来开发任何我们想要的东西,一个特定的功能可以用许多不同的方式来实现,那么为什么还要费力去使用委托,而我们可以在没有它们的情况下做得更好呢。

我认为这只是一种借口——试图掩盖这样一个事实,即许多开发者(即使是高级开发者)有时也只是不完全理解委托的概念。

挑战

有大量的文章、教程和博客解释委托。要再尝试解释那些已经被解释过很多次的东西,是非常困难的。
尽管如此,我还是决定写几篇文章,试图深入探讨委托的本质,并希望这能帮助程序员更好地理解它,并在他们的开发中使用它,从而使应用程序受益。这是第一篇文章,将向您介绍委托——它为什么被创建,它是什么,它背后是什么,以及您如何使用它。

委托的本质

让我们从词源学开始。谷歌英语词典解释了委托的含义:
Delegate: a person sent or authorized to represent others, in particular an elected. Representative sent to a conference. (一种被派遣或授权代表他人的人,特别是指被选派参加会议的代表。)

我想单独挑出“被派遣或授权代表他人”。
因此,C# 编程中的委托是一个被授权代表他人并可以为此目的被派出去的实体。这意味着我们有某些东西需要被代表。
那是什么呢?嗯,在我们的例子中,它是方法(函数)。方法驻留在某个地方。它可能属于一个对象,或者它可能只是一个 static 方法。如果方法需要有一个远程表示,它就使用委托。委托可以被发送到离所代表的方法很远的地方,但如果需要,委托可以很容易地调用该方法,无论它们相距多远。那么,为什么引入“委托”这个实体呢?主要是为了能够远程调用任何方法。它还允许我们将方法作为参数传递。

我们从来不会对常规对象可以作为函数参数感到惊讶,但方法呢?方法/函数可以作为参数传递吗?
直接来说,不行。
但是(这就是委托的力量)它的授权代表(委托)可以被传递代替。
好了,现在该进入正题了。到目前为止,我们知道委托代表一个方法(函数)。但让我们逐步分析如何使用委托。

步骤 1:声明委托

public delegate void RemoteOperation();

微软以一种非常面向对象的方式处理了 C# 委托的创建。与 C++ 不同,C++ 中的类似功能是通过函数指针实现的,而在我们的例子中,当编译器遇到上述代码时,它会创建一个类。
不要惊讶。当你声明一个委托时,编译器会在后台创建一个类,即使你不能明确地看到它。创建的类名将是 RemoteOperation 。根据编译器版本,它会继承自 MulticastDelegate 类(所有最新版本)或 Delegate 类。MulticastDelegate 类继承自 Delegate 类。它有构造函数,这意味着你必须使用 new() 关键字来实例化它。基类 Delegate MulticastDelegate 类之间存在一些内部关系。如果你是一个忠实的面向对象程序员,你一定听说过由 Gang of Four (GOF) 首次引入的编程模式。MulticastDelegate -> Delegate 是结构组合模式的一个完美例子。这并不是我们当前的话题,但概念非常简单:Delegate 类代表组件。MulticastDelegate 类代表复合体。MulticastDelegate (作为复合类)内部实现了一个 private Delegate 类对象数组。你可以根据自己的意愿向这个数组添加或删除 MulticastDelegate Delegate )对象。基类 Delegate 有一组属性和方法。它有一个名为 Invoke() 的方法,该方法具有 delegate 签名。这个签名是从你的 declare 语句中获取的。在我们的例子中,Invoke 方法不接受任何参数,并且返回 void 。它有两个非常有价值的属性:

  1. Target – 用于保存实现该方法的对象的引用
  2. Method – 用于保存该对象的 MethodInfo 对象

我们稍后会详细介绍这些属性。


注意

如果委托的声明不同(见下文)

public delegate int DifferentSignatureDelegate (int id , string name ); 

Invoke 方法返回整数,并接收整数和 string 作为参数。
换句话说,Invoke 方法模仿了你的委托声明。


现在,让我们准备一个我们将来希望由委托代表的方法。
它将是一个简单的 static 函数 DoNothing() ,它不接受任何参数,并返回 void

public static void DoNothing()
{
Console.WriteLine( "Nothing in nothing out, no action taken");
}

步骤 2:实例化委托

RemoteOperation operationVoid = new RemoteOperation(DoNothing); 

编译器创建一个 RemoteOperation 类的实例。
正如你所看到的,我们使用了 new 关键字,并且传递给这个构造函数的参数是函数本身,而不是一个 string
还记得 Invoke 方法吗?
在实例化之前,编译器会验证 DoNothing 的签名是否与 RemoteOperation 类的 Invoke 方法的签名相同。
如果验证失败,编译器会返回一个错误:No overload for "somename" matches delegate "delegatename"。如果验证成功,则会创建 RemoteOperation 类型的委托对象。然后编译器会创建一个 Delegate 类的新实例。它将实例的 Target 属性设置为 null ,因为委托代表一个 static 函数。
如果不是使用 static 函数,而是用对象实例函数为新的委托提供了参数,那么 Target 属性将拥有对包含函数的对象的引用。Method 属性将被设置为 Void DoNothing()。之后,它将 Delegate 的实例添加到 RemoteOperation 对象的内部 Delegate 数组中。

Delegates1.gif

这张图不是一个标准的 UML 图,它只是展示了我们在实例化委托时内部发生的事情。

新创建的对象拥有代表实际函数 DoNothing() 所需的所有信息。

  1. 它有这个函数签名。
  2. 它知道这个函数驻留在哪里。
  3. 它可以调用该函数。

好消息是:我们可以将这个对象用作函数的参数。

步骤 3:执行委托

执行委托(调用它所代表的函数)非常简单。

operationVoid(); 

这个语句等同于

operationVoid.Invoke();

此时,程序会找到函数,无论它驻留在何处,并调用它。
委托可以有多个表示吗? 答案显而易见:是的,可以。
一个 delegate 对象可以代表任意数量的函数,只要所有这些函数都具有相同的签名。如您所知,这是通过 Delegate 类对象的内部数组实现的。您可以在调试时查看此数组,并通过 GetInvocationList() 方法获取它。让我们创建一个带有方法的类。

public class SomeClass
{
public void AnotherFunction (){}
}

然后我们实例化这个类。

SomeClass o1 = new SomeClass (); 

并将类的引用添加到我们的委托中。

operationVoid += new RemoteOperation(o1.AnotherFunction); 

图 2 显示了 operationVoid 对象内部的内容。

Delegates2.gif

当你调用时——委托中的所有函数都将按照它们在列表中的顺序被调用(执行)。你也可以通过以下方式从调用列表中移除一个函数:

operationVoid -= new RemoteOperation(o1.AnotherFunction); 

这将从 Delegate 对象数组中移除与该函数对应的 Delegate 对象。

示例

理论讲够了,现在让我们来写点代码。这个例子的想法是使用一个遥控器来打开或关闭电视。我用了一个简单的 C# 控制台应用程序。在应用程序的主命名空间中,我首先声明了一个 public 委托。

public delegate void RemoteOperation(); 

Television 类有一个简单的构造函数,用于设置电视型号,并且有两个方法 TurnOn TurnOff ,里面有简单的消息。

public delegate void RemoteOperation();
	
/// <summary>
/// Presents a TV of a particular model
/// </summary>
public class Television	
{
    private string model;
			
    /// <summary>
    /// Returns TV model
    /// </summary>
    public string Model { get { return model; } }
		
    /// <summary>
    /// instantiates TV
    /// </summary>
    /// <param name="model"></param>
    public Television( string model)
    {	
        this.model = model;	
    }
			
    /// <summary>
    /// Turns TV on
    /// </summary>
		
    public void TurnOn()		
    {
        Console.WriteLine( this.model + " is on");
    }
			
    /// <summary>
    /// Turns TV off
    /// </summary>
    public void TurnOff()
    {
        Console.WriteLine( this.model + " is off");
    }
}	 

TV owner 类有两个只读属性 TV Name

/// <summary>
/// Presents TV owner
/// </summary>
public class TvOwner
{
    private Television myTV;
    private string name;
			
    /// <summary>
    /// Returns owner name
    /// </summary>
    public string Name { get { return name; } }
			
    /// <summary>
    /// Returns owner's TV
    /// </summary>
    public Television TV { get { return myTV; } }
			
    /// <summary>
    /// Instantiates owner
    /// </summary>
    /// <param name="name"></param>
    /// <param name="tvModel"></param>
    public TvOwner( string name, string tvModel)
    {
        this.name = name;
        myTV = new Television(tvModel);
    }
}	

Friend 类有一个 RemoteOperation 类型的 private 变量,它是一个 delegate 类型。
它还有两个方法:

  1. SetRemoteOperation
  2. RemoveRemoteOperation

这些方法接收一个 RemoteOperation 类型的参数,并设置 private 变量 remote,通过添加引用函数到其中。电视的主人可以直接调用电视方法 TurnOn TurnOff ,但也可以通过遥控器调用。此外,他/她还可以将遥控器操作传递给朋友,现在朋友就可以运行电视了。这正是委托的用途。

/// <summary>
/// Presents a person who can use remote control
/// </summary>
public class Friend
{
    private RemoteOperation remote;
    private string name;
			
    /// <summary>
    /// Instantiates the friend
    /// </summary>
    /// <param name="name"></param>
    public Friend( string name)
    {
        this.name = name;
    }
			
    /// <summary>
    /// Sets remote operation for a particular device
    /// </summary>
    /// <param name="remote"></param>
    /// <param name="deviceName"></param>
    public void SetRemoteOperation( RemoteOperation remote, string deviceName)
    {
        this.remote += remote;
        Console.WriteLine( "RemoteOperation for " + deviceName + " set");
    }
			
    /// <summary>
    /// Removes remote operation for a particular device
    /// </summary>
    /// <param name="remote"></param>
    /// <param name="deviceName"></param>
    public void RemoveRemoteOperation( RemoteOperation remote, string deviceName)
    {
        this.remote -= remote;
        Console.WriteLine( "RemoteOperation " + deviceName + " removed");
    }
			
    /// <summary>
    /// Executes remote operation
    /// </summary>
    public void UserRemote()
    {
        if ( null != remote)
        {
             Console.WriteLine(name + " clicked remote '" + remote.GetType().FullName
             + " " + remote.Method.Name + "'");
             remote();
        }
    }    
}	 

要运行的程序

/// <summary>
/// Execution program class
/// </summary>
public class Program
{
    /// <summary>
    /// Main procedure
    /// </summary>
    /// <param name="args"></param>
    static void Main( string[] args)
    {
	//create tv owner:
	TvOwner owner = new TvOwner( "Ed", "Samsung");
				
	//create tv owner:
	TvOwner nowner = new TvOwner( "Nick", "Sony");
				
	//create friend:
	Friend friend = new Friend( "Alex");
					
	//instantiate delegate for turning tv on:
	RemoteOperation setTvOn = new RemoteOperation(owner.TV.TurnOn);
					
	//instantiate delegate for turning tv on:
	RemoteOperation aaa = new RemoteOperation(nowner.TV.TurnOn);
					
	//instantiate delegate for turnting tv off:
	RemoteOperation setTvOff = new RemoteOperation(owner.TV.TurnOff);
					
	//pass function turnOnTv to the friend:
	friend.SetRemoteOperation(setTvOn, owner.TV.Model);
					
	//pass function turnOnTv to the friend:
	friend.SetRemoteOperation(aaa, nowner.TV.Model);
					
	//friend is using the remote to turn TV
	friend.UserRemote();
					
	//remove remote operation from friend:
	friend.RemoveRemoteOperation(setTvOn, owner.TV.Model);
					
	//pass function turnOffTv to the friend:
	friend.SetRemoteOperation(setTvOff, owner.TV.Model);
				
	//friend is using the remote to turn TV
	friend.UserRemote();
					
	//instantiate delegate for static function:
	RemoteOperation operationVoid = new RemoteOperation(DoNothing);
					
	friend.RemoveRemoteOperation(setTvOff, "none");
					
	friend.SetRemoteOperation(operationVoid, "none");
					
	friend.UserRemote();
					
	//finish:
	Console.Read();
    }
			
    public static void DoNothing()
    {
	Console.WriteLine( "Nothing in nothing out, no action");
    }
}	

预期的控制台输出

delegatesoutpub.gif

此代码仅用于教学目的,如果您认为它缺乏专业光彩,请勿批评。

结论

现在很明显,当委托被实例化时,它是一个 MulticastDelegate 类型的对象。
这个对象的目的是保存具有特定签名的一个或多个方法的引用。这个签名是在委托声明期间设置的。
委托内部有一个指向每个方法的引用的数组,当委托运行 Invoke() 方法时,所有引用的方法都会按顺序被调用。因为它是一个赋值给变量的对象,我们可以像普通变量一样对待它,并将其作为参数传递。

待续...

© . All rights reserved.