泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用"。
简单地说,开发人员先定义好一个算法,比如排序、搜索、交换等。但是定义算法的开发人员并不设定该算法要操作什么数据类型;该算法可广泛地应用于不同类型的对象。然后,另一个开发人员只要指定了算法要操作的具体数据类型,就可以使用这个现成的算法了。
泛型有两种表现形式:泛型类型和泛型方法。
泛型类型:大多数算法都封装在一个类型中,CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。除此之外,CLR还允许创建泛型接口和泛型委托。
泛型方法:方法偶尔也封装有用的算法,所以CLR允许引用类型、值类型或接口中定义泛型方法。
两者都是表示API的基本方法(不管是指一个泛型方法还是一个完整的泛型类型),以致平时期望出现一个普通类型的地方出现一个类型参数。比如,List<T>,在类名之后添加一个<T>,表明它操作的是一个未指定的数据类型。定义泛型类型和方法时,它为类型指定的任何变量(比如 T)都称为类型参数(type parameter)。T代表一个变量名,在源代码中能够使用一个数据类型的任何位置 ,都能使用T。
类型参数是真实类型的占位符。在泛型声明中,类型参数要放在一堆尖括号内,并以逗号分隔。所以,在Dictionary<TKey, TValue>中,类型参数是TKey和TValue。使用泛型类型或方法时,要使用真实的类型代替。这些真实的类型称为类型实参(type argument)。
泛型为开发人员提供了以下优势:
1)源代码保护 使用一个泛型算法的开发人员不需要访问算法的源代码。然而,使用C++模板的泛型技术时,算法的源代码必须提供给准备使用算法的用户。
2)类型安全 将一个泛型算法应用于一个具体的类型时,编译器和CLR能理解开发人员的意图,并保证只有与制定数据类型兼容的对象才能随同算法使用。
3)更清晰的代码 由于编译器强制类型安全性,所以减少了源代码中必须进行的转型次数。
4)更佳的性能 在有泛型之前,要想定义一个常规化的算法,它的所有成员都要定义成操作Object数据类型。这其中就要有装箱和拆箱之间的性能损失。由于现在能创建一个泛型算法来操作一个具体的值类型,所以值类型的实例能以传值的方式传递,CLR不再需要只需任何装箱操作。由于不再需要转型,所以CLR不必检查尝试一次转型操作是否类型安全,同样提高了代码的允许速度。
一、 Framework类库中的泛型
二、Wintellect的Power Collections库
Power Collections库由Wintellect制作,这个库有一系列集合类构成,任何人都可以免费下载和使用。
集合类名称 | 说明 |
BigList<T> | 有序T对象集合。操作100个以上的数据项是,效率非常高 |
Bag<T> | 无序T对象的集合,集合进行了哈希处理,并允许重复项 |
OrderedBag<T> | 有序T对象的集合,允许重复值 |
Set<T> | 无序T数据项集合,不允许重复项。添加重复项后,会只保留一个 |
OrderedSet<T> | 有序T数据项的集合,不允许重复项 |
Deque<T> | 双端队列(double-ending queue)。类似于一个列表,但在起始处添加/删除数据项时,比列表更高效 |
OrderedDictionary<TKey,TValue> | 字典,其中的键进行了排序,每个键都有一个对应的值 |
MultiDictionary<TKey,TValue> | 字典,其中每个键都可以有多个值,对键进行了哈希处理,允许重复,而且数据项是无序的 |
OrderedMultiDictionary<TKey,TValue> |
字典,其中的键进行了排序,每个键都可以有多个值(同样进行了排序)。允许重复的键 |
三、泛型的基础结构
为了是泛型能够工作,Microsoft必须完成以下工作:
internal static class Program
{
private static void Main(string[] args)
{
Object o = null; // Dictionary<,> 是一个开放类型,有两个类型参数
Type t = typeof(Dictionary<,>); // 尝试创建该类型的一个实例 (失败)
o = CreateInstance(t);
Console.WriteLine(); // DictionaryStringKey<> 是一个开放类型,有一个类型参数
t = typeof(DictionaryStringKey<>); // 尝试创建该类型的一个实例 (失败)
o = CreateInstance(t);
Console.WriteLine(); // DictionaryStringKey<Guid> 是一个封闭类型
t = typeof(DictionaryStringKey<Guid>); // 尝试创建该类型的一个实例 (成功)
o = CreateInstance(t); // Prove it actually worked
Console.WriteLine("Object type=" + o.GetType()); Console.ReadKey();
} private static Object CreateInstance(Type t)
{
Object o = null;
try
{
o = Activator.CreateInstance(t);
Console.Write("已创建 {0} 的实例", t.ToString());
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
}
return o;
} // A partially specified open type
internal sealed class DictionaryStringKey<TValue> :
Dictionary<String, TValue>
{
}
}
最后显示地结果为:
internal sealed calss GenericTypeThatReqiresAnEnum<T> {
static GenericTypeThatReqiresAnEnum() {
if ( !typeof (T).IsEnum) {
throw new ArgumentException("T must be an enumerated type")
}
}
}
CLR提供了一个名为"约束"(constraint)的功能,可利用它更好地定义一个泛型类型来指出哪个类型实参是有效的。
List<DateTime> dt = new List<DateTime>();
一些开发人员可能首先定义下面这样的一个类:
internal sealed class DateTimeList : List<DataTime> {
//这里无需放任何代码!
}
然后就可以进一步简化创建:
DateTimeList dt = new DateTimeList ();
这样做表面上是方便了,但是决定不要单纯处于增强源代码的易读性类这样定义一个新类。这样会丧失类型同一性(identity)和相等性(equivalence)。如下:
Boolean sameType = (typeof(List<DateTime>) == (typeof(DateTimeList));
上述代码运行时,sameType会初始化为false,因为比较的是两个不同类型的对象。也就是说,假如一个方法的原型接受一个DateTimeList,那么不能将一个List<DateTime>传给它。然而,如果方法的原型接受一个List<DateTime>,那么可以将一个DateTimeList传给它,因为DateTimeList是从List<DateTime>派生的。
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
现在只想下面这行代码时,sameType会初始化为true:
Boolean sameType = (type(List<DateTime>) == (ypeof(DateTimeList));
还有,可以使用C#的隐式类型局部变量功能,让编译器根据表达式的类型来推断一个方法的局部变量的类型。
public interface IEnumerator<T> : IDisposable, IEnumerator{
T Current { get; }
}
下面的示例类型实现上述泛型接口,而且指定了类型实参。
internal sealed class Triangle : IEnumerator<Point> {
private Point[] m_Vertice; public Point Current { get { ... } }
}
下面实现了相同的泛型接口,但保持类型实参的未指定状态:
internal sealed class ArrayEnumerator<T> : IEnumerator<T> {
private T[] m_Vertice; public TCurrent { get { ... } }
}
五、泛型委托
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 IAsycResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object);
public virtual TReturn EndInvoke(IAsycResult result);
}
反编译后
建议尽量使用在FVL中预定义的泛型Action和Func委托。
public delegate TResult Func<in T, Out TResult>(T arg);
其中,泛型类型参数T用in关键字标记,这使它成为一个逆变量;泛型类型参数TResulr则用out关键字标记,这是它成为一个协变量。
Func<Object,ArgumenException> fn1 = null;
就可以将它转型为另一个泛型类型参数不同的Func类型:
Func<String,Exception> fn2 = fn1; //不需要显示转型
Exception e = fn("");
使用要获取泛型参数和返回值的委托时,建议尽量为逆变性和协变性指定in和out关键字。这样做不会有不良反应,并使你的委托能在更多的情形中使用。
public interface IEnumerator<out T> : IEnumerator {
Boolean MoveNext();
T Current{ get; }
}
由于T是逆变量,所以以下代码可以顺利编译:
//这个方法接受任意引用类型的一个IEnumerable
Int32 Count(IEnumerable<Object> collection) { ... }
//以下调用向Count传递一个IEnumerable<String>
Int32 c = Count(new[] { "Grant" });
七、泛型方法
定义泛型类、结构或接口时,这些类型中定义的任何方法都可引用由类型指定的一个类型参数。类型参数可以作为方法的参数,作为方法的返回值,或者作为方法内部定义的一个局部变量来使用。
internal sealed class FenericType<T> {
privete T m_value; public GenericType(T value) { m_value = value; } public TOutput Converter<TOutput>() {
TOutput resulr= (TOurput) Convert.ChangeType(m_value,typeof(TOutput));
return result;
}
}
1.泛型方法和类型推断
private static void CallingSwapUsingInference() {
Int32 n1 = , n2 = ;
Swap(ref n1, ref n2); //调用Swap<Int32> String s1 = "A";
Object s2 = "B";
Swap(ref s1, ref s2); //错误,不能推断类型
}
执行类型推断时,C#使用变量的数据类型,而不是由变量引用的对象的实际类型。
八、泛型和其他成员
在C#中,属性、索引器、事件、操作符方法、构造器和终结器(finalizer)本身不能有类型参数。但是,它们能在一个泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。
九、可验证性和约束
public static T Min<T>(T o1, T o2) where T : IComparable<T> {
if (o1.CompareTo(o2))< return o1;
return o2;
}
C#的where关键字告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型IComparable接口。有了这个约束,就可以在方法中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo。
internal sealed class OverloadingByArity {
// 可以定义一下类型
internal sealed class AType { }
internal sealed class AType<T> { }
internal sealed class AType<T1, T2> { } // 错误: 与没有约束的 AType<T> 起冲突
internal sealed class AType<T> where T : IComparable<T> { } // 错误: 与 AType<T1, T2> 起冲突
internal sealed class AType<T3, T4> { } internal sealed class AnotherType {
// 可以定义一下方法,参数个数不同:
private static void M() { }
private static void M<T>() { }
private static void M<T1, T2>() { } // 错误: 与没有约束的 M<T> 起冲突
private static void M<T>() where T : IComparable<T> { } // 错误: 与 M<T1, T2> 起冲突
private static void M<T3, T4>() { }
}
}
internal static class OverridingVirtualGenericMethod {
internal class Base {
public virtual void M<T1, T2>()
where T1 : struct
where T2 : class {
}
} internal sealed class Derived : Base {
public override void M<T3, T4>()
/*where T3 : struct
where T4 : class */
{
}
}
}
1 主要约束
internal static class PrimaryConstraintOfStream<T> where T : Stream {
public static void M(T stream) {
stream.Close(); // OK
}
}
有两个特殊的主要约束:class和struct。其中,class约束是指类型实参是一个引用类型。任何类类型、接口类型、委托类型或者数组类型都满足这个约束。例如:
internal static class PrimaryConstraintOfClass<T> where T : class {
public static void M() {
T temp = null; // 允许,T为引用类型
}
}
struct约束向编译器承诺一个指定的类型实参是值类型。包括枚举在内的任何值类型都满足这个约束。然而,编译器和CLR将任何System.Nullable<T>值类型都视为特殊类型。
internal static class PrimaryConstraintOfStruct<T> where T : struct {
public static T Factory() {
// 允许,因为值类型都有一个隐式无参构造器
return new T();
}
}
internal static class SecondaryConstraints
{
private static List<TBase> ConvertIList<T, TBase>(IList<T> list)
where T : TBase
{ List<TBase> baseList = new List<TBase>(list.Count);
for (Int32 index = ; index < list.Count; index++)
{
baseList.Add(list[index]);
}
return baseList;
} private static void CallingConvertIList()
{
//构造并初始化一个List<String>(它实现了IList<String>)
IList<String> ls = new List<String>();
ls.Add("A String"); // 将IList<String>转换成IList<Object>
IList<Object> lo = ConvertIList<String, Object>(ls); // 将IList<String>转换成IList<IComparable>
IList<IComparable> lc = ConvertIList<String, IComparable>(ls); // 将IList<String>转换成IList<IComparable<String>>
IList<IComparable<String>> lcs =
ConvertIList<String, IComparable<String>>(ls); // 将IList<String>转换成IList<Exception>
//IList<Exception> le = ConvertIList<String, Exception>(ls); // 错误
}
}
internal sealed class ConstructorConstraints
{
internal sealed class ConstructorConstraint<T> where T : new()
{
public static T Factory()
{
// 允许,因为值类型都有隐式无参构造器
// 而约束要求任何引用类型也要有一个无参构造器
return new T();
}
}
}
目前,Microsoft只支持无参构造器约束。
private void CastingAGenericTypeVariable1<T>(T obj)
{
Int32 x = (Int32)obj; // 错误
String s = (String)obj; // 错误
}
上述两行错误是因为T可以是任何任何类型,无法保证成功。
private void CastingAGenericTypeVariable2<T>(T obj)
{
Int32 x = (Int32)(Object)obj; // 不报错
String s = (String)(Object)obj; // 不报错
}
现在虽然能编译通过,但运行时也无法保证是正确的。
internal sealed class SettingAGenericTypeVariableToADefaultValue
{
private void SettingAGenericTypeVariableToNull<T>()
{
//T temp = null; // 错误, 值类型不能设置为null,可考虑使用default('T')
} private void SettingAGenericTypeVariableToDefaultValue<T>()
{
T temp = default(T); // 正确
}
}
private void ComparingAGenericTypeVariableWithNull<T>(T obj)
{
if (obj == null) { /* 对值类型来说,永远不会执行 */ }
}
如果T被约束成一个struct,C#编译器会报错。
private void ComparingTwoGenericTypeVariables<T>(T o1, T o2)
{
//if (o1 == o2) { } // 错误
}
5)泛型类型变量作为操作书使用
internal static class UsingGenericTypeVariablesAsOperands {
private 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"和"T"类型的操作数等。
[CLR via C#]12. 泛型的更多相关文章
-
.NET via C#笔记12——泛型
12 泛型 使用值类型作为参数的泛型容器,传入值类型的参数时,不需要进行装箱 12.1 FCL中的泛型 System.Array中提供了很多泛型方法 AsReadOnly BinarySearch C ...
-
【C#进阶系列】12 泛型
泛型是CLR和编程语言提供的一种特殊机制,它用于满足“算法重用” . 可以想象一下一个只有操作的参数的数据类型不同的策略模式,完全可以用泛型来化为一个函数. 以下是它的优势: 类型安全 给泛型算法应 ...
-
CLR via C#(16)--泛型
泛型就像是一个模板,常常定义一些通用的算法,具体调用时再替换成实际的数据类型,提高了代码的可重用性. 一.初识泛型 1. 简单实例 以最常用的FCL中的泛型List<T >为例: stat ...
-
【Clr in c#】泛型
使用泛型的好处是“代码重用”,极大的提高了开发效率,泛型为开发者提供了以下优势: 1,源代码保护 算法的源代码不需要提供给使用泛型算法的开发人员,使用c++模板的泛型技术需要提供.(目前c++模板的 ...
-
CLR类型设计之泛型(二)
在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平时的业务中用的不多,多用于封 ...
-
CLR类型设计之泛型(一)
在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义objece类型,但是使用object就要面对装箱和拆箱的操作,装箱和拆箱会很损耗性能,我 ...
-
CLR via C#关于泛型(Generics )的摘录
泛型,是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”. 简单的说,开发人员先定义好一个算法,比如排序.搜索.交换.比较或者转换等.但是,定义算法的开发人员并不设改算法 ...
-
重温CLR(八 ) 泛型
熟悉面向对象编程的开发人员都深谙面向对象的好处,其中一个好处是代码重用,它极大提高了开发效率.也就是说,可以派生出一个类,让他继承基类的所有能力.派生类只需要重写虚方法,或添加一些新方法,就可定制派生 ...
-
读<;<;CLR via C#>;>; 详谈泛型
1,什么是泛型? 答:泛型是类型的模板,类型是实例(对象)的模板.C#提供了5种泛型:类,接口,委托,结构和方法. 2,使用泛型有什么好处? 答:继承实现的是"代码重用",而泛型实 ...
随机推荐
-
struts2实现改变超链接的显示方式
问题来源:我们看别人家专业的网站的时候,会发现他们的超链接都是这样的http://www.cnblogs.com/ivictor/p/5522383.html 这是一个连接到某一篇文章的超链接,我们一 ...
-
Regex
1. regex with variable example: find the number and put a parenthese around the number. output: a(52 ...
-
iOS 10 因苹果健康导致闪退 crash-b
如果在app中调用了苹果健康,iOS10中会出现闪退.控制台报出的原因是: Terminating app due to uncaught exception 'NSInvalidArgumentEx ...
-
[itint5]最大子矩阵和
http://www.itint5.com/oj/#39 最大子矩阵和,复杂度O(n^3).利用了最大子段和的方法. int maxRectSum(vector<vector<int> ...
-
SpringMVC轻松学习-注解的使用(三)
根据上一讲的例子,我们下面就注解的使用进行详细说明. 我们采用sprng MVC开发项目时,通常都会采用注解的方式,这样可以大大提高我们的开发效率.实现零配置.下面我们从零开始重新做一个spring ...
-
专访笨叔叔:2019年可能是Linux年?(转)
链接:https://zhuanlan.zhihu.com/p/57815479 2017年9月<奔跑吧 Linux内核>一书出版后得到了广大Linux从业人员和爱好者(特别是从事Linu ...
-
docker学习笔记(四)-持久化数据,安装docker-compose
Docker 持久化数据 实战 compose 安装docker-compose
-
Instant Django 1.5 Application Development Starter
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic d ...
-
JWT的介绍解析
JWT的介绍解析 一.什么是JWT?了解JWT,认知JWT 首先jwt其实是三个英语单词JSON Web Token的缩写.通过全名你可能就有一个基本的认知了.token一般都是用来认证的,比如我们系 ...
-
django使用JWT保存用户登录信息
在使用前必须弄明白JWT的原理,原理可以看我的另一篇博文:https://www.cnblogs.com/chichung/p/9966027.html JWT的流程 1.签发JWT 在用户正确输入账 ...