泛型,是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。

简单的说,开发人员先定义好一个算法,比如排序、搜索、交换、比较或者转换等。但是,定义算法的开发人员并不设改算法要操作什么数据类型:改算法可广发地应用于不同类型的对象。然后,另一个开发人员只要指定了算法要操作的具体数据类型,就可以开始使用这个现成的算法了。例如,可用一个排序算法来操作Int32 和String等类型的对象。

1、大多数算法都封装在一个类型中,CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。除此之外,CLR还允许创建泛型接口和反省委托。所以,CLR允许在引用类型、值类型或接口中定义泛型方法。

private static void SomeMethod() {
// Construct a List that operates on DateTime objects
List<DateTime> dtList = new List<DateTime>();
// Add a DateTime object to the list
dtList.Add(DateTime.Now); // No boxing 不需要装箱
// Add another DateTime object to the list
dtList.Add(DateTime.MinValue); // No boxing 不需要装箱
// Attempt to add a String object to the list
dtList.Add("1/1/2004"); // Compile-time error 编译时错误
// Extract a DateTime object out of the list
DateTime dt = dtList[]; // No cast required  不需要转型
}

2、泛型的优势:

  • 源代码保护:使用一个泛型算法的开发人员不需要访问算法的源代码。
  • 类型安全:将一个泛型算法应用于一个具体的类型时,保证只有与指定数据类型兼容的对象才能同算法使用。若试图使用不兼容类型的一个对象,会造成编译时错误,或在运行时抛出异常。
  • 更加清晰的代码:由于编译器强制类型的安全性,减少了源代码中必须进行的转型次数。
  • 更佳的性能:减少不必要的装箱、拆箱;由于不再需要转型,所以CLR不必检查尝试的一次转型操作是否类型安全,提高代码的运行速度。

3、Framework类库中的泛型    泛型最明显的应用就是集合类。FCL已经定义了几个泛型集合类。其中大多数都在System.Collections.Generic和System.Collection.ObjectModel命名空间中。

4、泛型类型和继承: 泛型类型仍然是类型,所以它能从其他任何类型派生。由于List<T>是从Objec派生的,所以List<String> 和List<Guid>也从Object派生。

5、泛型接口:泛型的主要作用就是定义泛型的引用类型和值类型。然而,对泛型接口的支持对CLR来说也很重要。没有泛型接口,每次试图使用一个非泛型接口(如IComparable)来操纵一个值类型,都会发生装箱,而且会失去编译时的类型安全性。

以下泛型接口定义时FCL的一部分(在System.Collections.Generic 命名空间中):

public interface IEnumerator<T> : IDisposable, IEnumerator {
T Current { get; }
} 

下面的示例类型实现了上述泛型接口,而且指定了类型实参。注意,Triangle对象可枚举一组Point对象。还要注意,Current属性具有Point数据类型。

internal sealed class Triangle : IEnumerator<Point> {

private Point[] m_vertices;
// IEnumerator<Point>'s Current property is of type Point
public Point Current { get { ... } }
...
}

6、泛型委托:CLR支持泛型委托,目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法。此外,泛型委托允许一个值类型实例在传给一个回调方法时不执行任何装箱处理。

例如,假定像下面这样定义一个委托泛型:

public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key,TValue value);

编译器会将它转成一个类,该类在逻辑上可以这样表示:

public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate {
public CallMe(Object object, IntPtr method);
public virtual TReturn Invoke(TKey key, TValue value);
public virtual IAsyncResult BeginInvoke(TKey key, TValue value,
AsyncCallback callback, Object object);
public virtual TReturn EndInvoke(IAsyncResult result);
}

7、泛型方法:定义泛型类、结构或接口时,这些类型中定义的任何方法都可引用由类型指定的一个类型参数。类型参数可以作为方法的参数,作为方法的返回值,或者作为方法内部定义的一个局部变量来使用。

internal sealed class GenericType<T> {
private T m_value;
public GenericType (T value) {m_value=value;}
public TOutput Converter<TOutput>(){
TOutput result=(TOutput) Convert.ChangeType(m_value ,typeof(TOutput));
return result; //返回将value转换成TOutput类型之后的结果
}
}

  在这个例子中,GenericType类定义了自己的类型参数(T),Converter 方法也定义了自己的类型参数(TOutput)。这样的GenericType可以处理任何类型。Convert方法能将m_value字段引用的对象转换成任意类型——具体取决于传给它的类型实参是什么。泛型方法的存在,为开发人员提供了极大的灵活性。

Swap方法:

private static void Swap<T>(ref T o1, ref T o2){
T temp=o1;
o1=o2;
o2=temp; }

 可以在代码中像这样调用Swap:

private static void CallingSwap() {
Int32 n1 = , n2 = ;
Console.WriteLine("n1={0}, n2={1}", n1, n2);//1,2
Swap<Int32>(ref n1, ref n2);
Console.WriteLine("n1={0}, n2={1}", n1, n2);//2,1
String s1 = "Aidan", s2 = "Grant";
Console.WriteLine("s1={0}, s2={1}", s1, s2);//Aidan,Grant
Swap<String>(ref s1, ref s2);
Console.WriteLine("s1={0}, s2={1}", s1, s2);//Grant,Aidan
}

8、其他可验证问题

 1)、泛型类型变量的转型:将一个泛型类型变量转型为另一个类型是非法的,除非将其转型为与一个约束兼容的类型。

private static void CastingAGenericTypeVariable1<T>(T obj) {
Int32 x = (Int32) obj; // Error 无法将类型”T"转换为"int"
String s = (String) obj; // Error 无法将类型“T"转换为"string"
}

上述两行会造成编译器报错,因为T可能是任意类型,无法保证能成功转型。为了修改上述代码使其能编译通过,可以先转型为Object:

private static void CastingAGenericTypeVariable2<T>(T obj) {
Int32 x = (Int32) (Object) obj; // No error
String s = (String) (Object) obj; // No error
}

虽然代码现在能够编译,但CLR仍有可能在运行时抛出一个InvalidCastException异常。

如果试图转型为一个引用类型,还可以使用C#的as操作符。下面对代码进行了修改,将操作符与Sting配合使用(因为Int32是一个值类型):

private static void CastingAGenericTypeVariable3<T>(T obj) {
String s = obj as String; // No error
}

 2)、将一个泛型类型变量设为默认值

将泛型类型变量设为null是非法的,除非将泛型类型约束成一个引用类型。

private static void SettingAGenericTypeVariableToNull<T>() {
T temp = null; // CS0403 – Cannot convert null to type parameter 'T' because it could
// be a non-nullable value type. Consider using 'default(T)' instead
//无法将null转换为类型参数“T",因为它可能是不为null的值类型。请考虑改用default('T')
}

由于未对T进行约束,所以它可能是一个值类型,而将值类型的一个变量设为null是不可能的。如果T被约束成引用类型,将temp设为null就是合法的,代码能顺利编译并运行。

private static void SettingAGenericTypeVariableToDefaultValue<T>() {
T temp = default(T); // OK
}

以上代码中的default关键字告诉C#编译器和CLR的JIT编译器,如果T是一个引用类型,就将temp设为null;如果T是一个值类型,就将temp的所有位设为0。

 3)、将一个泛型类型变量与null进行比较

无论泛型类型是否被约束,使用==或!=操作符将一个泛型类型变量与null进行比较都是合法的。

private static void ComparingAGenericTypeVariableWithNull<T>(T obj) {
if (obj == null) { /* Never executes for a value type */ }
//对于值类型来说,永远不会执行到判断里
}

注:如果T被约束成为一个struct,C#编译器会报错。值类型的变量不能与null进行比较,因为结果始终是相同的。

 4)、两个泛型变量相互比较

如果泛型类型参数不是一个引用类型,对同一个泛型类型的两个变量进行比较是非法的。

private static void ComparingTwoGenericTypeVariables<T>(T o1, T o2) {
if (o1 == o2) { } // Error
}

在这个例子中,T为进行约束。虽然两个引用类型的变量相互比较是合法的,单两个值类型的变量相互比较是非法的,除非值类型重载了 ”==“ 操作符。

不允许将类型参数约束成一个具体的值类型,因为值类型隐式密封,不可能存在从值类型派生的类型。如果允许将类型参数约束成一个具体的值类型,那么泛型方法会被约束为只支持该具体类型,这还不如写一个非泛型的方法呢!

 5)、泛型类型变量作为操作数使用

最后要注意的是,将操作符应用于泛型类型的操作数,会出现大量的问题。不能将这些操作符(+,-,*,/)应用于泛型类型的变量,因为编译器在编译时无法确定类型。

private static T Sum<T>(T num) where T : struct {
T sum = default(T) ;
for (T n = default(T) ; n < num ; n++)
sum += n;
return sum;
}

将T约束成为一个struct,而且使用default(T)将sum 和n初始化为0.编译仍会报错。运算符”<","++","+="无法应用于“T"(和)”T"类型的操作数

CLR via C#关于泛型(Generics )的摘录的更多相关文章

  1. [19/03/23-星期六] 容器_ 泛型Generics

    一.概念 生活中的容器不难理解,是用来容纳物体的,程序中的“容器”也有类似的功能,就是用来容纳和管理数据. 数组就是一种容器,可以在其中放置对象或基本类型数据. ---优势:是一种简单的线性序列,可以 ...

  2. 泛型(Generics)

    Framework类库中的泛型 泛型可以使代码重用,提高开发效率 CLR允许在引用类型.值类型或接口中定义泛型方法: CLR允许创建泛型引用类型.泛型值类型(枚举不允许创建).泛型委托类型和泛型接口类 ...

  3. CLR via C#(16)--泛型

    泛型就像是一个模板,常常定义一些通用的算法,具体调用时再替换成实际的数据类型,提高了代码的可重用性. 一.初识泛型 1. 简单实例 以最常用的FCL中的泛型List<T >为例: stat ...

  4. 【Clr in c#】泛型

    使用泛型的好处是“代码重用”,极大的提高了开发效率,泛型为开发者提供了以下优势: 1,源代码保护  算法的源代码不需要提供给使用泛型算法的开发人员,使用c++模板的泛型技术需要提供.(目前c++模板的 ...

  5. [CLR via C#]12. 泛型

    泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用". 简单地说,开发人员先定义好一个算法,比如排序.搜索.交换等.但是定义算法的开 ...

  6. Java 泛型(Generics)

    Generics, 类似C++中的模版. 允许在定义类和接口的时候使用类型参数(type parameters), 声明的类型参数在使用的时候用具体的类型来替换. 如 ArrayList<Str ...

  7. CLR类型设计之泛型(二)

    在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平时的业务中用的不多,多用于封 ...

  8. CLR类型设计之泛型(一)

    在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义objece类型,但是使用object就要面对装箱和拆箱的操作,装箱和拆箱会很损耗性能,我 ...

  9. 重温CLR(八 ) 泛型

    熟悉面向对象编程的开发人员都深谙面向对象的好处,其中一个好处是代码重用,它极大提高了开发效率.也就是说,可以派生出一个类,让他继承基类的所有能力.派生类只需要重写虚方法,或添加一些新方法,就可定制派生 ...

随机推荐

  1. 在Mac OS环境下安装MySQL服务

    在Mac OS环境下安装MySQL服务 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我之前介绍过window环境下安装mysql服务,以及在Linux环境下安装mysql服务,今 ...

  2. Shell命令的执行顺序

    shell执行命令的步骤顺序如上图,看起来有些复杂. 当命令行被处理时,每一个步骤都是在Shell的内存里发生的;Shell不会真的把每个步骤的发生显示给你看. 所以,你可以假想这事我们偷窥Shell ...

  3. Neo4j社区版配置文件

    #*****************************************************************# Neo4j configuration## For more d ...

  4. Hbase学习03

    第3章 Hbase数据存储模型与工作组件 Data格式设计的的总体原则是按照需求要求,依据Hbase性能的相关标准规范和文件,并遵循“统一规范.统一数据模型.统一规划集群.分步实施”的原则,注重实际应 ...

  5. 写入MySQL报错超出 max_allowed_packet 的问题

    写入MySQL报错超出 max_allowed_packet 的问题. MySQL会根据配置文件会限制server接受的数据包的大小.如果写入大数据时,因为默认的配置太小,插入和更新操作会因为 max ...

  6. 算法实践——舞蹈链(Dancing Links)算法求解数独

    在“跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题”一文中介绍了舞蹈链(Dancing Links)算法求解精确覆盖问题. 本文介绍该算法的实际运用,利用舞蹈链(Dancin ...

  7. 历史SQL语句之一

    注释:查询该课程表下,所有的课程以及课程观看的人数统计 SELECT ml.`name` as lessonName,ml.category,ml.deleted,ml.teacherName, ml ...

  8. jquery中$.post()方法的简单实例

    在jqery中有这样一个方法,$.post()下面就这个方法做一个简单的实例: jQuery.post( url, [data], [callback], [type] ) :使用POST方式来进行异 ...

  9. 开源框架.netCore DncZeus学习(五)下拉树的实现

    千里之行,始于足下,先从一个小功能研究起,在菜单管理页面有一个下拉树,先研究下它怎么实现的 1.先找到menu.vue页面 惯性思维先搜索请选择三个字,原来是动态生成的 再向上找DropDown组件, ...

  10. 解决XP系统任务管理器显示不全

    我们在使用电脑的时候有的时候打开任务管理器会发现任务管理器显示不全. 当碰到这种情况怎么解决呢?任务管理器显示不全的原因又是那些呢? 这里就来为大家分享下为什么任务管理器会显示不全以及如何解决这个问题 ...