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

委托教程 - MC++ 和 C# - 双重视角

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (45投票s)

2001 年 10 月 16 日

CPOL

6分钟阅读

viewsIcon

256399

介绍并引导您完成 .NET 中委托的使用。比较和对比 MC++ 和 C# 在使用委托方面的不同方法。附带两种语言的示例。

引言

我个人最喜欢的老式 C 语言特性之一是函数指针。那些没用过函数指针的人错过了很多乐趣。当 C++ 出现时,我们也拥有了指向成员函数的指针。函数指针和指向成员函数的指针的基本问题在于,它们都不是类型安全的。.NET 框架在 System 命名空间中有一个名为 Delegate 的类。委托是 .NET 中函数指针和指向成员函数的指针的替代品。委托的优点在于它们是完全托管的对象,并且也是类型安全的。委托基本上封装了一个具有特定参数集和返回类型的方法。您只能在委托中封装一个与委托定义匹配的方法。委托可以封装类中的静态方法以及实例方法。

当委托封装单个方法时,它们被称为单播委托;当它们封装多个方法时,它们被称为多播委托。多播委托在事件处理程序中很有用。多播委托不应与委托数组混淆。多播委托继承自 MulticastDelegate 类,而 MulticastDelegate 类又是 Delegate 类的子类。调用多播委托时,封装的方法会按照添加到多播委托的顺序同步调用。托管 C++ 和 C# 提供了语言特定的功能,使我们能够直接使用委托,而无需调用 Delegate 类或 MulticastDelegate 类的成员方法。通过一些非常简单的示例,我将展示委托在托管 C++ 和 C# 中的用法。

基本操作

声明委托

在托管 C++ 中,我们使用 __delegate 关键字来声明委托。在 C# 中,我们使用 delegate 关键字。在两种情况下,编译器都会自动继承自 System::Delegate。单播委托和多播委托的声明方式没有区别。我推测内部将单播委托视为只有一个封装方法的多个委托。

//delegate declaration using Managed C++
__delegate String* DelegateAbc(); 
//delegate declaration using C#
public delegate String DelegateAbc();

将委托绑定到方法

对于单播委托,我们只需使用委托从 System::Delegate 继承的默认委托构造函数。该构造函数接受两个参数,第一个参数是要将方法绑定的对象,第二个参数是方法的地址。对于静态方法,第一个参数可以是 0。在 C# 中,情况进一步简化,我们不需要传递第一个参数。C# 编译器会为我们处理好。

//binding delegates using MC++
DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc); //instance method
DelegateAbc *d2 = new DelegateAbc(0,&Test::TestStatic); //static method
//binding delegates using C#
DelegateAbc d1 = new DelegateAbc (t1.TestAbc); //instance method
DelegateAbc d2 = new DelegateAbc (Test.TestAbc); //static method

对于多播委托,我们使用 Delegate.Combine 方法,该方法有两个重载。一个重载接受一个 Delegate 对象数组并组合它们。另一个重载接受两个 Delegate 对象并组合它们。两者都返回一个 Delegate 对象,我们需要将其转换为我们的委托类型。同样,C# 程序员非常轻松。C# 中已重载 + 和 += 运算符,因此只需使用 + 运算符即可将任意数量的委托相加。

//multi-cast delegate using MC++
d1 = static_cast<DelegateAbc*> (Delegate::Combine(d1,
         new DelegateAbc(t1,&Test::TestAbc)));
//multi-cast delegate using C#
d1 = d2 + new DelegateAbc (Test.TestAbc); //using the + operator
d1 += new DelegateAbc (Test.TestAbc); //using the += operator 

要从多播委托的调用列表中移除委托,我们使用 Delegate.Remove 方法。此方法接受两个参数。第一个参数是源委托,它可能包含一个或多个封装的方法。第二个参数是我们希望从多播委托中移除的委托对象。该方法返回一个 Delegate 对象,我们将其转换为预期的委托类型。我猜您可能已经猜到 C# 会有更简单的方法来处理这些事情。在 C# 中,- 和 -= 运算符已重载,因此您可以实际从多播委托中减去一个委托。

//removing a delegate from a multi-cast delegate - MC++
d1 = static_cast<DelegateAbc*>(Delegate::Remove(d1,d2));
//removing a delegate from a multi-cast delegate - C#
d1 = d1 - d2; //using the - operator
d1 -= d3; //using the -= operator

调用委托

当我们调用委托时,封装的方法会按照附加到委托的顺序同步调用。在托管 C++ 中,这是通过调用名为 Invoke 的方法来实现的。此方法由编译器添加到我们的委托类中,并且将具有与我们的委托相同的签名。在 C# 中,我们甚至不需要担心这么多,我们只需要调用一个与我们的委托对象同名的方法,并传递任何必需的参数。此处描述的 Invoke 机制基于早期绑定。我们确切地知道委托签名是什么,因此我们可以调用我们的委托。您可能想知道,Invoke 方法实际上是由相应的编译器添加的,而不是从 Delegate 类继承的。对于后期绑定调用,您可以使用 DynamicInvoke 方法。但本文不涉及后期绑定调用,因为它超出了本文的范围和范畴。

//invoking a delegate with MC++
d1->Invoke("4"); //passing a string as argument
d2->Invoke(); //no arguments
//invoking a delegate with C#
d1("4");  //passing a string as argument
d2(); //no arguments

现在我们将看到一些小型示例程序,它们将使事情变得更清晰。编译并运行程序,并尝试弄清楚您获得的输出是否有意义。如果您感到困惑,不用太担心,只需再次阅读本文,然后思考一段时间。事情会慢慢变得清晰。MSDN 上也有一些关于委托的优秀文章,它们将为您提供更多启发。

程序 1

在此程序中,我们将看到如何声明和使用单播委托。我们的委托接受一个 String 作为参数,并返回一个 String。我们将首先将对象的实例方法分配给委托,然后调用委托。然后,我们将一个类的静态方法分配给同一个委托对象,然后再次调用委托。

/* Managed C++ Sample */

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;

__delegate String* DelegateAbc(String* txt);

__gc class Test
{
public:
    String* TestAbc(String* txt)
    {
        Console::WriteLine(txt);
        return "Hello from TestAbc";
    }
    static String* TestStatic(String* txt)
    {
        Console::WriteLine(txt);
        return "Hello from TestStatic";
    } 
};
int wmain(void)
{ 
    Test *t1 = new Test();
    DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc);
    Console::WriteLine(d1->Invoke("First call"));
    d1 = new DelegateAbc(0,&Test::TestStatic);
    Console::WriteLine(d1->Invoke("Second call")); 
    return 0;
}
/* C# Sample */

using System;

class DelegateDemo
{
    delegate String DelegateAbc(String txt);

    public String TestAbc(String txt)
    {
        Console.WriteLine(txt);
        return "Hello from TestAbc";
    }
    public static String TestStatic(String txt)
    {
        Console.WriteLine(txt);
        return "Hello from TestStatic";
    } 
    static void Main()
    {
        DelegateDemo t1 = new DelegateDemo();
        DelegateAbc d1 = new DelegateAbc(t1.TestAbc);
        Console.WriteLine(d1("First call"));
        d1 = new DelegateAbc(DelegateDemo.TestStatic);
        Console.WriteLine(d1("Second call")); 

    }
}

程序 2

现在我们将看到一个使用多播委托的示例。我们的委托不接受参数并返回 void。我们将首先创建两个单播委托,一个基于实例方法,另一个基于静态方法。然后,我们将通过组合这两个委托来创建我们的多播委托。现在我们调用我们的多播委托。从输出中,您应该能够弄清楚封装的方法调用顺序。现在,我们从多播委托中删除其中一个委托,然后再次调用它。输出应与您对委托工作原理的理解相符。

/* Managed C++ Sample */

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;

__delegate void DelegateAbc();

__gc class Test
{
public:
    void TestAbc()
    {
        Console::WriteLine("This is from TestAbc"); 
    }
    static void TestStatic()
    {
        Console::WriteLine("This is from the static method"); 
    } 
};
int wmain(void)
{ 
    Test *t1 = new Test();
    DelegateAbc *d1 = new DelegateAbc(t1,&Test::TestAbc); 
    DelegateAbc *d2 = new DelegateAbc(0,&Test::TestStatic);
    d1 = static_cast<DelegateAbc*> (Delegate::Combine(d1,d2));
    d1->Invoke();
    d1 = static_cast<DelegateAbc*>(Delegate::Remove(d1,d2));
    Console::WriteLine();
    d1->Invoke();
    return 0;
}
/* C# Sample */

using System;

class DelegateDemo
{
    delegate void DelegateAbc();

    public void TestAbc()
    {
        Console.WriteLine("This is from TestAbc"); 
    }
    public static void TestStatic()
    {
        Console.WriteLine("This is from the static method"); 
    } 

    static void Main()
    {
        DelegateDemo t1 = new DelegateDemo();
        DelegateAbc d1 = new DelegateAbc(t1.TestAbc); 
        DelegateAbc d2 = new DelegateAbc(DelegateDemo.TestStatic);
        d1 = d1+d2;
        d1();
        d1 -= d2;
        Console.WriteLine();
        d1();
    }
}

程序 3

在此程序中,我们将看到如何将委托对象作为参数传递给方法。这很巧妙的地方在于,被调用的方法完全不知道传递的委托正在引用什么。在我们的小例子中,我们有一个委托,它接受一个 int 并返回一个 int。我们将编写两个可以分配给委托的方法,一个返回传入数字的平方,另一个返回传入数字的立方。

/* Managed C++ Sample */

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;

__delegate int DelegateAbc(int);

__gc class Test
{
public:
    int SquareMe(int i)
    {
        return i*i;
    }
    int CubeMe(int i)
    {
        return i*i*i;
    } 
    void ShowResult(DelegateAbc* d, String* s,int i)
    {
        Console::WriteLine("{0} of {1} is {2}",s,
            i.ToString(),d->Invoke(i).ToString());
    }
};
int wmain(void)
{ 
    Test *t = new Test();
    t->ShowResult(new DelegateAbc(t,&Test::SquareMe),"Square",7);
    t->ShowResult(new DelegateAbc(t,&Test::CubeMe),"Cube",7);
}
/* C# Sample */

using System;

class DelegateDemo
{
    delegate int DelegateAbc(int i);
    
    public    int SquareMe(int i)
    {
        return i*i;
    }
    public int CubeMe(int i)
    {
        return i*i*i;
    } 
    void ShowResult(DelegateAbc d, String s,int i)
    {
        Console.WriteLine("{0} of {1} is {2}",s,i,d(i));
    }

    static void Main()
    {
        DelegateDemo t = new DelegateDemo();
        t.ShowResult(new DelegateAbc(t.SquareMe),"Square",7);
        t.ShowResult(new DelegateAbc(t.CubeMe),"Cube",7);
    }
}

结论

总而言之,委托差不多相当于函数指针,不同之处在于委托是对象并且是类型安全的。与函数指针不同,委托可以引用类中的静态方法和实例方法。委托继承自 MulticastDelegate。编译器会将一个 Invoke 方法添加到您的委托对象中,该方法具有与委托相同的签名和返回类型。委托可以是单播的或多播的。多播委托是通过组合多个委托形成的。委托可以作为参数传递给函数。

委托的绝妙之处在于,它们不关心它们正在引用的类成员函数。它只关心传入的参数和返回类型与其自身匹配。因此,我们可以将委托用于黑盒调用,在这种情况下,我们不知道委托指向哪个成员函数。委托在事件处理程序中非常有用。当引发事件时,通过委托调用订阅类的事件处理程序。

© . All rights reserved.