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

C# 中的委托 - 深入探究。第 4 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (66投票s)

2010 年 10 月 12 日

CPOL

8分钟阅读

viewsIcon

86981

经典、泛型、Lambda。不同的代码技术。

背景

这是我关于委托的第四篇文章。我鼓励您阅读前三篇

.NET 中的更多委托

随着 .NET Framework 的不同版本出现,委托的使用变得越来越可取。为了满足更多需求,.NET 3.5 引入了一个全新的委托世界。下面我将列出 .NET 3.5 Framework 中提供的委托名称和描述的表格(.NET 4.0 甚至有更多的内置委托)。为了使用委托,首先必须声明它。在声明时,我们定义了委托可能代表的函数的签名。有了这些预定义的委托,我们可以跳过声明部分。.NET 不仅提供了一组方便的委托,还将**泛型**数据类型嵌入到了许多委托中。您可以看到,许多委托将**泛型**类型作为参数或/和返回值。这非常方便。您可以将代码中的**泛型**类型替换为任何类型,编译器将处理其余的。

委托 描述
操作 封装一个不接受任何参数且不返回值的方法。
Action<T> 封装一个接受单个参数且不返回值的方法。
Action<T1, T2>> 封装一个具有两个参数且不返回值的方法。
Action<T1, T2, T3> 封装一个接受三个参数且不返回值的方法。
Action<T1, T2, T3, T4> 封装一个具有四个参数且不返回值的方法。
AppDomainInitializer 表示应用程序域初始化时要调用的回调方法。
AssemblyLoadEventHandler 表示处理 AppDomain 的 AssemblyLoad 事件的方法。
AsyncCallback 引用在相应的异步操作完成时要调用的方法。
Comparison<T> 表示比较两个相同类型对象的方法。
ConsoleCancelEventHandler 表示将处理 System.ConsoleCancelKeyPress 事件的方法。
Converter<TInput, TOutput> 表示将一个对象从一种类型转换为另一种类型的方法。
CrossAppDomainDelegate DoCallBack 用于跨应用程序域调用。
EventHandler 表示处理没有事件数据的事件的方法。
EventHandler<TEventArgs> 表示处理事件的方法。
Func<TResult> 封装一个没有参数并返回 TResult 参数指定的类型的值的方法。
Func<T, TResult> 封装一个有一个参数并返回 TResult 参数指定的类型的值的方法。
Func<T1, T2, TResult> 封装一个有两个参数并返回 TResult 参数指定的类型的值的方法。
Func<T1, T2, T3, TResult> 封装一个有三个参数并返回 TResult 参数指定的类型的值的方法。
Func<T1, T2, T3, T4, TResult> 封装一个有四个参数并返回 TResult 参数指定的类型的值的方法。
Predicate<T> 表示定义一组标准并确定指定对象是否满足这些标准的方法。
ResolveEventHandler 表示处理 AppDomain 的 TypeResolveResourceResolveAssemblyResolve 事件的方法。
UnhandledExceptionEventHandler 表示处理由应用程序域未处理的异常引发的事件的方法。

目标

在本文中,我想讨论处理自定义委托和预定义委托的不同技术。有很多方法可以解决问题。我个人不喜欢这么说,因为我家里有两只猫,我爱它们。但毫无疑问,在程序中使用委托有许多不同的方法。

我们可以从表格中对委托进行分类

  1. 泛型函数和方法
    • 操作
    • 功能
    • 转换器
    • 比较
    • 谓词
  2. 事件处理程序
    • AssemblyLoadEventHandler
    • ConsoleCancelEventHandler
    • EventHandler
    • EventHandler<TEventArgs>
    • ResolveEventHandler
    • UnhandledExceptionEventHandler
  3. 其他
    • AppDomainInitializer
    • AsyncCallback(我们已经熟悉了这个委托 - 第 2 部分)
    • CrossAppDomainDelegate

我的意图是讨论前三个委托 ActionFuncConverter。我想演示如何以不同的方式使用这些委托。最终,编译器会生成非常相似的代码,但从编程角度来看,这项技术存在巨大的差异。

Action 和 Func

Action 表示任何可能接受最多四个参数(.NET 4.0 中为 8 个)并且返回 void 的函数。Action<T1,T2>T1T2 是参数,可以是任何数据类型。FuncAction 相同,但它有一个任何类型的返回值。Func<T1,T2,TResult>T1T2 是任何类型的参数,TResult 是返回值。所以 ActionFunc 之间唯一的区别是返回值。这对我示例来说无关紧要。我将在示例中使用 Action,您可以轻松地将这种技术应用于 Func

经典委托示例

public delegate void ShootingHandler(int times);

这是类

public class GunClassicDelegate
{
    private ShootingHandler shoot;

    public string Name { get; set; }
    public GunClassicDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = new ShootingHandler(ShootAutomatic);
                break;
            case "paint gun":
                shoot = new ShootingHandler(ShootPaint);
                break;
            default:
                shoot = new ShootingHandler(ShootSingle);
                break;
        }
    }
    public void Fire(int times)
    {
        shoot(times);
    }

    private void ShootAutomatic(int times)
    {
        Console.WriteLine("Automatic shooting: ");
        for (int i = 0; i < times; i++)
            Console.Write("Biff...") ;
        Console.WriteLine();
    }

    private void ShootSingle(int times)
    {
        Console.WriteLine("Single action shooting: ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bang");
    }

    private void ShootPaint(int times)
    {
        Console.WriteLine("Splashing paint ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bloop");
    }
}

在项目命名空间中,我声明了我的委托类型

public delegate void ShootingHandler(int times);

显然,它返回 void 并接受一个整数。在 GunClassicDelegate 类中,我有一个名为“shoot”的变量,它的类型就是这个委托。

private ShootingHandler shoot;

在构造期间,根据传入的名称,我将以下私有函数之一赋值给该变量

  1. ShootAutomatic
  2. ShootSingle
  3. ShootPaint

您可以看到,这是可以完成的,因为这些函数与委托的签名匹配。我有一个公共函数 Fire,在该函数中调用委托。非常简单。没问题。

使用预先构建的 .NET Action 委托

public class GunGenericDelegate
{
    private Action<int> shoot;

    public string Name { get; set; }
    public GunGenericDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = ShootAutomatic;
                break;
            case "paint gun":
                shoot = ShootPaint;
                break;
            default:
                shoot = ShootSingle;
                break;
        }
    }

    public void Fire(int times)
    {
        shoot(times);
    }

    private void ShootAutomatic(int times)
    {
        Console.WriteLine("Automatic shooting: ");
        for (int i = 0; i < times; i++)
            Console.Write("Biff...");
        Console.WriteLine();
    }

    private void ShootSingle(int times)
    {
        Console.WriteLine("Single action shooting: ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bang");
    }

    private void ShootPaint(int times)
    {
        Console.WriteLine("Splashing paint ");
        for (int i = 0; i < times; i++)
            Console.WriteLine("Bloop");
    }
}

您可以看到,不再需要委托声明了。我的私有变量 shoot 被声明为

private Action<int> shoot;

这告诉编译器,一个返回 void 并接受整数的委托可以放入“shoot”变量中。

构造函数看起来更简单。它只是将函数赋值给变量

shoot = ShootAutomatic;

使用预先构建的 .NET Action 内联委托

public class GunGenericInLineDelegate
{
    public string Name { get; set; }
    private Action<int> shoot;
    public void Fire(int times) { shoot(times); }

    public GunGenericInLineDelegate(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {

            case "automatic gun":
                shoot = delegate(int times)
                {
                    Console.WriteLine("Automatic shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.Write("Biff...");
                    Console.WriteLine();
                };
                break;
            case "paint gun":
                shoot = delegate(int times)
                {
                    Console.WriteLine("Splashing paint ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bloop");
                };
                break;
                default:
                    shoot = delegate(int times)
                    {
                        Console.WriteLine("Single action shooting: ");
                        for (int i = 0; i < times; i++)
                            Console.WriteLine("Bang");
                    };
                    break;
            }
        }
    }

在此示例中,我决定放弃这三个私有函数。这大大简化了我的代码。现在,功能是在构造期间定义的。语法很简单。

shoot = delegate(int times)
{ //the functionality goes here };

编译器知道“shoot”是一个委托类型。它允许您使用“delegate”关键字并将其用作内联函数声明。非常整洁。

使用预先构建的 .NET Action 内联 LAMBDA 委托

public class GunGenericInLineLambda
{

    public string Name { get; set; }
    public Action<int> shoot;
    public void Fire(int times) { shoot(times); }

    public GunGenericInLineLambda(string name)
    {
        Name = name;
        switch (Name.ToLower())
        {
            case "automatic gun":
                shoot = (times) =>
                {
                    Console.WriteLine("Automatic shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.Write("Biff...");
                    Console.WriteLine();
                };
                break;
            case "paint gun":
                shoot = (times) =>
                {
                    Console.WriteLine("Splashing paint ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bloop");
                };
                break;
            default:
                shoot = (times) =>
                {
                    Console.WriteLine("Single action shooting: ");
                    for (int i = 0; i < times; i++)
                        Console.WriteLine("Bang");
                };
                break;
        }
    }
}

**Lambda** 表达式早已被引入编程界,现在 **C#** 程序员在处理委托时非常喜欢使用它们。

它们背后的逻辑与上一个示例相同。主要区别在于声明函数。我使用 **Lambda** 表达式而不是 delegate 函数。

(i) => { //functionality goes here}

这是一个 lambda 委托调用。(i) 是委托需要的参数列表。如果委托不需要参数,它将是 (). => {.......} lambda 语法:从参数到函数体。

在花括号内,您可以使用参数的名称定义功能。

再次,由于 ActionFunc 在实现上没有区别,我决定不为 Func 创建单独的示例。

这是一个可运行的程序

public class ActionExample
{
    static void Main(string[] args)
    { 
        //part - classic:
        Console.WriteLine("Shooting classic delegate:");
        GunClassicDelegate gunClassic1 = new GunClassicDelegate("automatic gun");
        GunClassicDelegate gunClassic2 = new GunClassicDelegate("paint gun");
        GunClassicDelegate gunClassic3 = new GunClassicDelegate("hand gun");

        gunClassic1.Fire(4);
        gunClassic2.Fire(4);
        gunClassic3.Fire(4);

        //part - generic delegate:
        Console.WriteLine("Shooting generic delegate:");
        GunGenericDelegate gunGeneric1 = new GunGenericDelegate("automatic gun");
        GunGenericDelegate gunGeneric2 = new GunGenericDelegate("paint gun");
        GunGenericDelegate gunGeneric3 = new GunGenericDelegate("hand gun");

        gunGeneric1.Fire(4);
        gunGeneric2.Fire(4);
        gunGeneric3.Fire(4);

        //part - generic inline delegate:
        Console.WriteLine("Shooting generic inline delegate:");
        GunGenericInLineDelegate gunGenericInLine1 = 
              new GunGenericInLineDelegate("automatic gun");
        GunGenericInLineDelegate gunGenericInLine2 = 
              new GunGenericInLineDelegate("paint gun");
        GunGenericInLineDelegate gunGenericInLine3 = 
              new GunGenericInLineDelegate("hand gun");

        gunGenericInLine1.Fire(4);
        gunGenericInLine2.Fire(4);
        gunGenericInLine3.Fire(4);

        //part - generic lambda delegate:
        Console.WriteLine("Shooting generic lambda delegate:");
        GunGenericInLineLambda gunGenericInLineLambda1 = 
              new GunGenericInLineLambda("automatic gun");
        GunGenericInLineLambda gunGenericInLineLambda2 = 
              new GunGenericInLineLambda("paint gun");
        GunGenericInLineLambda gunGenericInLineLambda3 = 
              new GunGenericInLineLambda("hand gun");

        gunGenericInLineLambda1.Fire(4);
        gunGenericInLineLambda2.Fire(4);
        gunGenericInLineLambda3.Fire(4);

        Console.Read();
    }
}

运行它时,您会发现每个示例的输出都没有区别。

转换器

委托 Converter<TInput,TOutput>Array.ConvertAll<TInput,TOutput> (TInput [] arraytoconvert, Converter<TInput, TOutput> converter) 中工作。乍一看,它似乎有点复杂,但实际上非常简单。如果您想将对象数组从一种数据类型转换为另一种数据类型,您需要使用 Array 对象的泛型函数 ConvertAll。此函数接受两个参数。第一个参数是要转换的数据类型的数组。第二个参数是执行转换的函数的委托。这个委托就是 .NET 创建者为您方便准备的 Converter 委托。

这是我解释处理器的代码

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string firstname, string lastname)
    {
        FirstName = firstname;
        LastName = lastname;
    }
}

public class PersonInfo
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName { get { return FirstName + " " + LastName; } }

    public static PersonInfo ConvertToPersonInfo(Person person)
    {
        PersonInfo res = new PersonInfo();
        res.FirstName = person.FirstName;
        res.LastName = person.LastName;
        return res;
    }
}

我有两个类,PersonPersonInfo。它们之间只有一个很小的区别,但对于编译器来说,它们是两个不同的类。如果我想将 Person 类的数组转换为 PersonInfo 类的数组,我必须使用 Converter 委托。所以我创建了一个静态函数 ConvertToPersonInfo,它接受一个 Person 类型的对象并返回一个 PersonInfo 类型的对象。此函数属于 PersonInfo 类。在接下来的代码示例中,我将展示三个不同的 Main 方法。在代码中,它们有不同的名称:MainClassicMainLambdaMainDelegate。您必须为您的程序选择其中一个。它们演示了不同的委托技术。

MainClassic 引用了在 PersonInfo 类中预先创建的 PersonInfo.ConvertToPersonInfo 函数。最后两个 Main 方法描述了内联转换,而无需为此操作预先创建特殊函数。而且,正如您所见,您也可以使用 **Lambda** 语法来实现委托。

class Program    {
    static void MainClassic(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people, 
            new Converter<Person,PersonInfo>(PersonInfo.ConvertToPersonInfo));
    }

    static void MainLambda(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people, (p) => {
            PersonInfo pi = new PersonInfo();
            pi.LastName = p.LastName;
            pi.FirstName = p.FirstName;
            return pi;
        });
    }

    static void MainDelegate(string[] args)
    {

        Person[] people = { new Person("ed", "g"), 
                            new Person("al", "g") };

        PersonInfo[] newpeople = Array.ConvertAll(people, delegate(Person p)
        {
            PersonInfo pi = new PersonInfo();
            pi.LastName = p.LastName;
            pi.FirstName = p.FirstName;
            return pi;
        });
    }
}

再次,我希望您意识到这些方法不能同时工作;您必须将它们重命名为 Main 并在您的 **Console** 应用程序中逐个运行它们。它们都创建了一个包含两个 Person 类型对象的数组。MainClassic 使用 new 关键字以经典风格创建了一个新委托,并将 PersonInfo.ConvertToPersonInfo 函数赋值给委托。MainDelegate 使用内联委托创建,而 MainLambda 也是内联的,但更倾向于使用 **Lambda** 语法来使用委托。

结论

关于委托的主题还有更多内容,但我想在此停止。我只是想给您一个想法。正如您所见,在代码中使用委托时,您有许多不同的选项。如果没有合适的预定义委托,您可以声明自己的并使用它们。您可以利用内联编码,而不是创建命名函数并将其赋值给委托。您可以使用 **lambda** 语法,这对于委托来说非常方便。这是我关于委托系列的最后一部分。如果您喜欢这篇文章,请投票。

© . All rights reserved.