C# 中的委托 - 深入探究。第 4 部分
经典、泛型、Lambda。不同的代码技术。
背景
这是我关于委托的第四篇文章。我鼓励您阅读前三篇
- C# 中的委托 - 深入探究。第 1 部分 - 如何理解和使用委托。
- C# 中的委托 - 深入探究。第 2 部分 - 如果需要,可以异步调用委托。
- C# 中的委托 - 深入探究。第 3 部分 - 更多关于委托。认识事件。
.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.Console 的 CancelKeyPress 事件的方法。 |
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 的 TypeResolve 、ResourceResolve 和 AssemblyResolve 事件的方法。 |
UnhandledExceptionEventHandler |
表示处理由应用程序域未处理的异常引发的事件的方法。 |
目标
在本文中,我想讨论处理自定义委托和预定义委托的不同技术。有很多方法可以解决问题。我个人不喜欢这么说,因为我家里有两只猫,我爱它们。但毫无疑问,在程序中使用委托有许多不同的方法。
我们可以从表格中对委托进行分类
- 泛型函数和方法
操作
功能
转换器
比较
谓词
- 事件处理程序
AssemblyLoadEventHandler
ConsoleCancelEventHandler
EventHandler
EventHandler<TEventArgs>
ResolveEventHandler
UnhandledExceptionEventHandler
- 其他
AppDomainInitializer
AsyncCallback
(我们已经熟悉了这个委托 - 第 2 部分)CrossAppDomainDelegate
我的意图是讨论前三个委托 Action
、Func
、Converter
。我想演示如何以不同的方式使用这些委托。最终,编译器会生成非常相似的代码,但从编程角度来看,这项技术存在巨大的差异。
Action 和 Func
Action
表示任何可能接受最多四个参数(.NET 4.0 中为 8 个)并且返回 void
的函数。Action<T1,T2>
:T1
和 T2
是参数,可以是任何数据类型。Func
与 Action
相同,但它有一个任何类型的返回值。Func<T1,T2,TResult>
:T1
、T2
是任何类型的参数,TResult
是返回值。所以 Action
和 Func
之间唯一的区别是返回值。这对我示例来说无关紧要。我将在示例中使用 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;
在构造期间,根据传入的名称,我将以下私有函数之一赋值给该变量
ShootAutomatic
ShootSingle
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 语法:从参数到函数体。
在花括号内,您可以使用参数的名称定义功能。
再次,由于 Action
和 Func
在实现上没有区别,我决定不为 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;
}
}
我有两个类,Person
和 PersonInfo
。它们之间只有一个很小的区别,但对于编译器来说,它们是两个不同的类。如果我想将 Person
类的数组转换为 PersonInfo
类的数组,我必须使用 Converter
委托。所以我创建了一个静态函数 ConvertToPersonInfo
,它接受一个 Person
类型的对象并返回一个 PersonInfo
类型的对象。此函数属于 PersonInfo
类。在接下来的代码示例中,我将展示三个不同的 Main
方法。在代码中,它们有不同的名称:MainClassic
、MainLambda
、MainDelegate
。您必须为您的程序选择其中一个。它们演示了不同的委托技术。
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** 语法,这对于委托来说非常方便。这是我关于委托系列的最后一部分。如果您喜欢这篇文章,请投票。