0

0

c#中的泛型

php中文网

php中文网

发布时间:2016-07-06 13:30:22

|

1580人浏览过

|

来源于php中文网

原创

这篇文章主要来讲讲c#中的泛型,因为泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。当我多次看到自己团队的代码中包含着大量的非泛型集合,隐式的装箱和拆箱操作时,我都会建议他们补一补泛型基

      这篇文章主要来讲讲c#中的泛型,因为泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。当我多次看到自己团队的代码中包含着大量的非泛型集合,隐式的装箱和拆箱操作时,我都会建议他们补一补泛型基础。

     1,什么是泛型

    • 泛型是c#2中非常重要的一个新特性,它增强了代码的可读性,将大量的安全检查从执行期转移到编译期,从而提高代码的安全性和性能。从根本上来说,泛型实现了类型和方法的参数化。

     2,为什么要使用泛型,泛型解决了什么问题

我们先来看看下面的代码(代码只是为了演示泛型,没有实际的意义),看看有什么问题?   

 1 class PRogram
 2     {
 3         static void Main(string[] args)
 4         {
 5             ArrayList array = new ArrayList();
 6             array.Add(1);
 7             array.Add(2);
 8             array.Add(3);
 9             ArrayList resultArray = NumberSqrt(array);
10             foreach (var item in resultArray)
11             {
12                 Console.WriteLine(item);
13             }
14         }
15         public static ArrayList NumberSqrt(ArrayList array)
16         {
17             ArrayList sqrtArray = new ArrayList();
18             foreach (var item in array)
19             {
20                 sqrtArray.Add(Math.Sqrt((double)(int)item));
21             }
22             return sqrtArray;
23 
24         }
25     }
View Code
    • 首先ArrayList是一个非泛型集合,它需要的参数是object,那么我如果往集合里面装配其它的类型,在编译时也无法判断错误(比如说我装配一个字符串进去,完全是可以编译成功的)。
    • 由于ArrayList需要的是一个object,那么在你将值类型装配到集合中时,会进行隐式装箱操作,在使用集合中的数据时,需要进行拆箱操作。从而影响应用程序的性能。(关于值类型装箱请参考c#中的引用类型和值类型)。
    • 代码可以可读性差,完全看不明白ArrayList应该装配什么类型。

下面来看看泛型是如何解决这些问题的:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             List<double> array = new List<double>();
 6             array.Add(1);
 7             array.Add(2);
 8             array.Add(3);
 9             List<double> resultArray = NumberSqrt(array);
10             foreach (var item in resultArray)
11             {
12                 Console.WriteLine(item);
13             }
14         }
15         public static List<double> NumberSqrt(List<double> array)
16         {
17             List<double> sqrtArray = new List<double>();
18             foreach (var item in array)
19             {
20                 sqrtArray.Add(Math.Sqrt((double)(int)item));
21             }
22             return sqrtArray;
23 
24         }
25 
26     }
View Code
    • 由于List只能装配double,所以当你装配其它的类型时编译器会报错,提高代码的安全性。
    • 由于List只能装配double类型时,这样的话就避免了编译器隐式的装箱和拆箱操作,提高应用程序的性能。
    • 一眼就可以看出集合需要装配的类型,提高代码的可读性。
    • NumberSqrt方法在这里没什么用去,如果你要写一个算法,把NumberSqrt改为泛型方法:NumberSqrt(比如说排序算法),可以实现算法重用。

     3,如何使用泛型:语法和规则

上面已经说了什么是泛型,以及为什么要用泛型,下面我们来聊聊如何使用泛型

    • 泛型的语法:泛型主要有泛型方法(例如:static void Swap(ref T lhs, ref T rhs)),泛型接口( 例如: public interface IEnumerable : IEnumerable),泛型委托( 例如: public delegate void Action(T obj);)
    • 在FCL中内建了许多泛型的接口定义(常用的接口都在System.Collections.Generic),为了使泛型能够正常的工作,微软的开发人员将会做如下工作,
      • 创建新的IL指令,使之能够识别类型参数。
      • 改变元数据的格式,使之能够识别泛型参数和泛型方法。
    • 开放类型和封闭类型:当为一个泛型类型没有指定实际的数据类型时,就称为开放类型,例如List,对于任何开放类型都不能创建该类型的实例,例如下面的代码在运行是将会报错未:经处理的异常:  System.ArgumentException: 无法创建 Consoleapplication2.Demo`1[T] 的实例,因为 Type.ContainsGenericParameters 为 True。
 1     public class Demo { }
 2     class Program
 3     {
 4         static void Main(string[] args)
 5         {
 6             object o = null;
 7             Type t = typeof(Demo<>);
 8             o = Activator.CreateInstance(t);
 9         }
10     }
View Code

               如果为泛型类型的所有类型实参传递的都是实际数据类型,类型就称为封闭类型,CLR允许构造封闭类型的实例。

    • 泛型类型和继承:泛型类型只是一个特殊的类型,所以它可以从其他任何类型派生。比如List是从object派生的,那么List也是从object派生的。换句话说,类型实参的指定和继承层次结构没有任何关系-理解这一点,有助于你判断那些转型是否能够进行,哪些转型是不能进行的。
    • 编译器如何解决“代码爆炸”的问题:在使用泛型类型或者方法方法时,Clr获取IL代码,用指定的类型实参进行替换。如果Clr为每种不同的类型/方法组合都生成不同的本地代码,他可能会增大应用程序的工作集,从而影响性能。我们将这种现象称为”代码爆炸“。那么编译器又是如何解决这个问题的呢?编译器认为所有引用类型的实参是完全相同的,所以代码能够共享(为什么这么说呢?是因为所有引用类型的实参或者变量实际只是指向堆上的对象的指针,而对象的指针全部是以相同的方式来操操纵的)。但是如果某个类型实参是值类型的话,CLR就得专门为那个值类型生成本地代码,这是值类型的大小不定。
    • 泛型接口:为什么要使用泛型接口呢?因为你每次试图使用一个非泛型接口来操作一个值类型时,都会发生装箱,从而失去编译时的类型安全性。所以CLR提供了对泛型接口的支持。下面来看一个例子:
       1    // 摘要:
       2     //     支持在泛型集合上进行简单迭代。
       3     //
       4     // 类型参数:
       5     //   T:
       6     //     要枚举的对象的类型。
       7     public interface IEnumerator<out T> : IDisposable, IEnumerator
       8     {
       9         // 摘要:
      10         //     获取集合中位于枚举数当前位置的元素。
      11         //
      12         // 返回结果:
      13         //     集合中位于枚举数当前位置的元素。
      14         T Current { get; }
      15     }
      View Code
    • 泛型委托:CLR之所以要支持泛型委托,主要目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法,并且保证了将一个值类型实例传递给一个回调方法时不执行任何装箱处理。
    • 泛型方法:定义泛型类,结构或者接口时,这些类型中定义的任何方法都可引用由类型指定的一个类型参数。类型参数可以作为方法的参数/返回值,或者作为方法内部的一个局部变量来使用。下面来看一个例子:
       1  class Program
       2     {
       3         static void Main(string[] args)
       4         {
       5             MyClass<string> myclass = new MyClass<string>();
       6             myclass.ShowInfo("myClass");
       7         }
       8     }
       9 
      10     class MyClass
      11     {
      12         public void ShowInfo(Ta a)
      13         {
      14             Type t = typeof(Ta);
      15             Console.WriteLine(t.FullName + "非泛型方法");
      16         }
      17         public void ShowInfo(Ta a)
      18         {
      19             Type t = typeof(Ta);
      20             Console.WriteLine(t.FullName + "泛型方法");
      21         }
      22         public void ShowInfo(Ta a, Tb b)
      23         {
      24             Type t = typeof(Tb);
      25             Console.WriteLine(t.FullName);
      26         }
      27     }
      View Code

       泛型方法的类型推断:c#语法中包含大量""符号,所以导致代码的可读性和可维护性降低了,所以为了改变这种情况,c#编译器支持在调用一个方法时进行类型推断。例如下面的代码:myclass.ShowInfo("myClass", myclass);会调用ShowInfo(string a, string b);

    • 可验证性和约束:编译器在编译泛型代码时,会对它进行分析,以确保代码适用于当前已有或将来可能定义的任何类型。请查看下面的方法:
      1      public static Boolean MethodTakingAnyType(T o) {
      2             T temp = o;
      3             Console.WriteLine(o.ToString());
      4             Boolean b = temp.Equals(o);
      5             return b;
      6         }
      View Code

      看这个方法里面有一个临时变量temp。方法里面执行两次变量赋值和几次方法调用,无论T是值类型还是引用类型还是接口类型或者是委托类型这个方法都能工作。这个方法适用于当前存在的所以类型,也适用于将来可能定义的任何类型。比如你再看一下下面这个方法:

      1      public static T MethodTakingAnyType(T o1,T o2) {
      2             if (o1.CompareTo(o2) < 0)
      3             {
      4                 return o1;
      5             }
      6             else {
      7                 return o2;
      8             }
      9         }
      View Code

      在编译时会报如下错误(error CS0117:"T"不包含"CompareTo"的定义),因为并不是所有的类型都提供了CompareTo方法。那么在什么情况下T应该是什么类型呢?幸好,编译器和CLR支持一个称为约束的机制,可利用它使泛型变得真正有用!

      天工大模型
      天工大模型

      中国首个对标ChatGPT的双千亿级大语言模型

      下载
      • 约束的语法:请看下面这个方法名字后面的where
        1 public static Boolean MethodTakingAnyType(T o) where T : struct
        2         {
        3             T temp = o;
        4             Console.WriteLine(o.ToString());
        5             Boolean b = temp.Equals(o);
        6             return b;
        7         }
        View Code

        c#的where关键字告诉编译器,为T指定的任何类型都必须是值类型。所以当你为T指定其它类型时,编译器会报错,例如你指定为string类型时(MethodTakingAnyType("");)它会报错误    1    类型“string”必须是不可以为 null 值的类型才能用作泛型类型或方法

      • 主要约束:一个类型参数可以指定零个或一个主要约束(也就是第一个约束)。主要约束可以是一个引用类型,它标识了一个没有密封的类,不能指定以下特殊类型:System.Object;System.Array;System.Delegate;System.MulticastDelegate;System.ValueType;System.Enum;
      • 次要约束:一个类型参数可以指定零个或多个次要约束。次要约束是一个接口类型,指定一个接口类型约束时,是告诉编译器类型实参必须实现这个接口。还有一种次要约束称为类型参数约束,有时也称为裸类型约束,看下面的代码
        1  public static Boolean MethodTakingAnyType(T o) where T : TBase
        2         {
        3             T temp = o;
        4             Console.WriteLine(o.ToString());
        5             Boolean b = temp.Equals(o);
        6             return b;
        7         }
        View Code

        T类型参数由TBase类型单数约束,也就是说不管T为什么类型,都必须兼容于TBase指定的类型实参。

      • 构造器约束:一个类型参数可以指定零个或一个构造器约束。指定构造器约束相当于告诉编译器一个指定的类型实参必须要实现了公共无参构造器。请看下面的的代码:
        1       public static Boolean MethodTakingAnyType(T o) where T : new()
        2         {
        3             T temp = o;
        4             Console.WriteLine(o.ToString());
        5             Boolean b = temp.Equals(o);
        6             return b;
        7         }
        View Code
      • 其它可验证性的问题:1,泛型类型变量的转型。下面的类型编译时出错,因为T可能为任何类型,无法保证能转型成功!
        1     public static void MethodTakingAnyType(T o)
        2         {
        3             int x = (int)o;
        4             string s = (string)o;
        5         }
        View Code

        2,将一个泛型类型变量设置为默认值:o = default(T);这样的话,不管T为值类型还是引用类型都可以成功,如果T为引用类型时就设置为null,如果T为值类型时将默认值设为0;

        3,将一个泛型类型变量与Null进行比较:使用==或者=!将一个泛型类型变量于null进行比较都是合法的。但是如果T为值类型时,o永远都不会为null查看下面的代码:
        1        public static void MethodTakingAnyType(T o)
        2         {
        3             if (o == null) { 
        4             //do something
        5             }
        6         }
        View Code

        4,将两个泛型类型变量相互比较:如果T是值类型,下面的代码就是非法的。

        1    public static void MethodTakingAnyType(T o1,T o2)
        2         {
        3             if (o1 == o2) { 
        4             
        5             }
        6         }
        View Code

     4,泛型在使用过程的注意事项

    • 泛型不支持协变性
    • 缺乏操作符约束或者“数值”约束
    • 缺乏泛型属性,索引器和其它成员类型

 


本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

2

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

golang 循环遍历
golang 循环遍历

本专题整合了golang循环遍历相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.31

Golang人工智能合集
Golang人工智能合集

本专题整合了Golang人工智能相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

76

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

73

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

67

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

19

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 5万人学习

Rust 教程
Rust 教程

共28课时 | 5.1万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号