ToB企服应用市场:ToB评测及商务社交产业平台

标题: 泛型的约束不止一面 [打印本页]

作者: 祗疼妳一个    时间: 2022-9-17 08:38
标题: 泛型的约束不止一面
1.介绍

泛型中的约束,其实就是针对类型参数的约束,限制类型参数的选择只能在某个特定范围内。其中的体现包括:限制类型参数必须是一个结构、限制类型参数必须是某个具体类型、限制类型参数必须派生自某个基类等等。在默认情况下,定义的泛型没有任何约束,这意味着在调用泛型时,可以使用任何数据类型作为类型参数。如果定义了约束,则在应用端调用泛型时,不传入符合约束条件的类型参数,编译器将提示错误。通过这种约束实现了编译前类型检查,确保了泛型在运行时对类型参数使用的安全性。
以上说的这种限制性的作用,只能体现约束表面的用意,这种用意是比较浅显易懂。但实际上泛型的约束还有另一层的用意:“定义约束可以告知编译器,类型参数具备了哪些能力”。我们在为某个泛型类或泛型方法编码时,面向的类型参数T,其实类似是一个模糊神秘的事物,因为你根部不会知道它有什么能力(属性、方法等成员),如果你想在编写泛型时使用类型参数T的某些能力,那么你就可以通过定义约束来实现。例如,你想要类型参数T调用“比较大小”的方法从而帮助你实现排序算法,你就可以定义一个泛型的约束:“要求类型参数必须实现IComparer接口”。这样一来,你的类型参数T,就能够在你编写泛型类的代码中“.”出Compare(比较的方法)。
基于上面对类型参数定义约束的用意分析,我针对约束主要的作用总结出以下两点:
以上通过文字描述的形式介绍了泛型中类型参数的约束,为了更加形象的体会其中的含义和作用,下面我将通过代码示例的形式介绍类型参数定义约束的使用方式。
2.示例

假设我们在一个开发游戏的背景下,游戏比较简单,其中目前有两个职业:剑士和狙击手,并且后期随着游戏的普及会增加更多的职业。由于是战斗类型的游戏,所有每个职业都会使用特定的武器进行攻击,从而实现战斗的体验。对于该游戏职业设计相关的类图如下:
 
由于这只是一个为了讲解泛型约束的一个示例,所以并没有采用复杂的设计。由于剑士和狙击手两个职业都有相同的攻击行为,故而将攻击定义为了一个接口,具体的攻击内容将交由这两个职业类去实现。根据以上的类图的设计,相应的代码如下:
  1. 1     //攻击接口
  2. 2     interface IAttack
  3. 3     {
  4. 4         void MeleeAttacks();  //近战攻击
  5. 5     }
  6. 6
  7. 7     //剑士
  8. 8     class Swordman: IAttack
  9. 9     {
  10. 10         public Swordman() => Sword = "倚天剑";
  11. 11
  12. 12         public string Sword { get; set; }
  13. 13         public void MeleeAttacks()
  14. 14         {
  15. 15             Console.WriteLine("使用{0}进行刺击。", Sword);
  16. 16         }
  17. 17     }
  18. 18
  19. 19     
  20. 20     //狙击手
  21. 21     class Sniper : IAttack
  22. 22     {
  23. 23         public Sniper() => Gun = "98k狙击步枪";
  24. 24         public string Gun { get; set; } //枪
  25. 25  
  26. 26         public void MeleeAttacks()
  27. 27         {
  28. 28             Console.WriteLine("使用{0}进行射击。", Gun);
  29. 29         }
  30. 30     }
复制代码
3.能力

假设我们的游戏示例是一款战斗类型的游戏,那么其中所有的职业都需要进行战斗。对于这个共同的行为,正好可以借鉴泛型的使用思想:即不同类型存在相同处理逻辑,那么可以使用泛型作为一个代码模板,从而实现不同类型的通用化处理。我们计划将战斗的行为定义成一个泛型类,由这个泛型类统一实现各个职业的战斗。然而在编写战斗泛型类的时候,由于战斗必须要使用职业的攻击方法,但是我们在内部调用类型参数T并不能获取到相应的方法,编译器视乎将类型参数T看成了一个object类型。

怎么办?究竟如何能够在战斗泛型类中调用游戏角色的攻击方法呢?这个时候就轮到本文的主题“泛型的约束”闪亮登场了,接下来我们将针对战斗泛型类定义一个约束,在泛型类中使用类型参数T调用出攻击的方法:
  1. 1     /// <summary>
  2. 2     /// 各个职业的战斗
  3. 3     /// </summary>
  4. 4     class Combat<T> where T :IAttack
  5. 5     {
  6. 6         public Combat(T combatant)
  7. 7         {
  8. 8             _combatant = combatant;
  9. 9         }
  10. 10         private T _combatant;//参战者
  11. 11
  12. 12         public void Action()
  13. 13         {
  14. 14             Console.WriteLine("战斗开始");
  15. 15             _combatant.MeleeAttacks();
  16. 16             Console.WriteLine("战斗结束");
  17. 17         }
  18. 18
  19. 19     }
复制代码
果不其然,成功的在战斗泛型类中调用了角色的攻击方法,这是因为设置了约束,类型参数T就可以根据约束的类型获取相应的能力。这一点也正好可以印证了本文开头总结泛型约束的作用之一:对内部使用提供了更多能力,从而丰富功能的实现”。示例的代码已经基本编写完成,接下来我们就可以在应用端,使用战斗泛型类针对不同的角色实施战斗行为了。
 
4.安全

假设你的小伙伴正在另一头在编写游戏中关于NPC部分的代码,他得知你编写了可以实现各种职业进行战斗的泛型类,于是乎他悄悄的使用一个NPC的对象来使用你的战斗泛型类。但是NPC在实际的需求中并没有实现攻击接口。NPC类的代码结构如下:

我们在假定,泛型的约束不能够对外部传入的类型参数(NPC类)起到限制作用。那么这个NPC的“战斗”情况可想而知,NPC是没有主动攻击的方法的,他盲目的使用战斗泛型类,只会无情的面临“死亡”。还好,我们定义的类型参数约束对此进行了把关,我们约束的规则是:要求类型参数必须实现攻击接口。而NPC并没有实现攻击接口,所以对于NPC使用战斗泛型类时编译器会提示错误。
 
通过NPC滥用泛型类的这个示例,就可以从分的体现出本文开头总结泛型约束的作用之一:“对外部使用形成了限制条件,从而确保泛型的类型安全”。
5.结语

理解泛型的约束,可能会觉得它是很语义化、片面化的东西。殊不知,其实泛型约束在实际中最有作用的是,为类型参数提供能力,让我们在编码的过程中更有针对性。所以学习不能只求表面,必须通过反复思考,才能让获取的知识更加立体。
对于泛型约束的使用方式,除了本文示例中要求实现一个特定接口的方式,另外还有很多使用方式。我们不可能将每一个使用细节了然于心,但是必须搞清楚事物的本质,以致于知道为什么有它的存在、在什么样的情况下使用它。当不同的应用场景发生时,我们在结合当下应用场景的实际情况,通过查阅文档来制定具体的方针。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4