1. 概述
- 泛型可以创建独立于被包含类型的类和方法。
- 泛型不仅限于类,还可用于接口和方法。
- 泛型优点:
- 性能:List<T>类使用时定义类型,故不再进行装箱和拆箱操作,即性能高。
- 类型安全:使用时定义了类型,因此可以通过编译检测出不符合的类型。
- 二进制代码重用:定义一次,但可以用许多不同的类型实例化。
- 代码的扩展
- 命名约定:
- 泛型类型的名称用字母T作为前缀
- 没有特殊要求,且只使用一个泛型类型,就可以用字符T作为泛型类型的名称
- 如有特殊要求(如实现接口或派生自基类),或使用两个或以上泛型类型,就使用描述性的名称
public delegate void EventHandler<TEventArgs>(objcet sender, TEventArgs e);
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class SortedList<TKey, TValue> {}
2. 泛型类的功能
(1) 默认值:default关键字
- 不能把null赋予泛型类型 => 原因是泛型类型既可以实例化为引用类型,又可以实例化为值类型。而null只能用于引用类型。
- default关键字,将null赋予引用类型,将0赋予值类型。
public T GetDocument() { T doc = defalut(T); lock(this) { doc = documentQueue.Dequeue(); } return doc; }
(2) 约束:
- 泛型类需要调用泛型类型中的方法,必须添加约束。
- 泛型支持的约束类型
- 可以合并多个约束,如where T : IFoo,new() 即类型T必须实现IFoo接口,且必须有一个默认构造函数
- 给泛型类型添加约束时,最好包含泛型参数名称的一些信息
// TDocument 来代替 T // 对于编译器而言,参数名不重要,但更具有可读性 // 即TDocument类型必须实现IDocument接口 public class DocumentManager<TDocument> where TDocument : IDocument { //... }
(3) 继承
- 泛型类型可以实现泛型接口(IEnumerable<T>),也可以派生自一个类
- 派生类可以是泛型类或非泛型类
public class Base<T> { } // 派生自泛型基类 public class Derived<T> : Base<T> { } // 派生自指定基类的类型 public class Derived<T> : Base<string> { }
- 还可以创建一个部分的特殊操作
public class Query<TRequest, TResult> { } public StringQuery<TRequest> : Query<TRequest, TResult> { }
(4) 静态成员:泛型类的静态成员只能在类的一个实例中共享。
// StaticDemo<T>类包含静态字段X public classs StaticDemo<T> { public static int x; } // 同时对不同类型使用泛型类 StaticDemo<string>.x = 4; StaticDemo<int>.x =5; WriteLine(StaticDemo<string>.x); // writes 4
3. 泛型接口
(1) 协变与抗变:指对参数和返回值的类型进行转换
- 协变:参数类型的转换。可以理解成:父类 -> 子类。父类的对象用子类替换,也可以理解成子类当父类用。例如,函数Act的输入参数为object类型,实际操作中我们可以将string类型的对象传给函数。
private void button1_Click(object sender, EventArgs e) { string str = "这是一个string类型的实例, 函数Act的参数为object, 这里有协变的应用"; Act(str); } void Act(object obj) { return ; }
- 抗变:方法的返回类型的转换。可以理解成:子类 -> 父类。子类的对象用父类替换,也可以理解成父类当子类用。抗变也常常翻译为逆变。例如,函数Func的返回类型为string,我们可以将返回的值赋给object对象。
private void button2_Click(object sender, EventArgs e) { //注意这里:Func的返回类型为string, obj的类型为object, string类型继承自object object obj = Func(); } string Func() { return "这里有抗变的应用"; }
(2) 泛型接口的协变:用out关键字标注,泛型接口就是协变的。即在接口实现代码里面,T只能用作返回类型,不能用作参数类型。
(3) 泛型接口的抗变:用in关键字标注,泛型接口就是抗变的。即在接口实现代码里面,T用作方法的输入。
//泛型接口支持协变、逆变和不支持协变、逆变的对比 //1-定义一个接口IFoo,既不支持协变,也不支持逆变。 interface IFoo<T> { void Method1(T param); T Method2(); } //实现接口IFoo public class FooClass<T> : IFoo<T> { public void Method1(T param) { Console.WriteLine(default(T)); } public T Method2() { return default(T); } } //2-定义一个接口IBar支持对参数T的协变 interface IBar<out T> { T Method(); } //实现接口IBar public class BarClass<T> : IBar<T> { public T Method() { return default(T); } } //3-定义一个接口IBaz支持对参数T的逆变 interface IBaz<in T> { void Method(T param); } //实现接口IBaz public class BazClass<T> : IBaz<T> { public void Method(T param) { Console.WriteLine(param.ToString()); } } //---------------应用--------------- //1-定义两个有继承关系的类型,IParent和SubClass interface IParent { void DoSomething(); } public class SubClass : IParent { public void DoSomething() { Console.WriteLine("SubMethod"); } } //2-按照协变的逻辑,分别来使用IFoo和IBar //IFoo 不支持对参数T的协变 IFoo<SubClass> foo_sub = new FooClass<SubClass>(); IFoo<IParent> foo_parent = foo_sub;//编译错误 //IBar 支持对参数T的协变 IBar<SubClass> bar_sub = new BarClass<SubClass>(); IBar<IParent> bar_parent = bar_sub; //3-按照逆变的逻辑,分别来使用IFoo和IBaz。 //IFoo 对参数T逆变不相容 IFoo<IParent> foo_parent = null; IFoo<SubClass> foo_sub = foo_parent;//编译错误 //IBaz 对参数T逆变相容 IBaz<IParent> baz_parent = null; IBaz<SubClass> baz_sub = baz_parent;
部分说明转自:https://www.cnblogs.com/icyJ/archive/2012/11/16/covariant.html
4. 泛型结构:非常类型于泛型类,知识没有继承特性。
- .NET Framework的一个泛型结构是Nullable<T>
- 主要用于将数据库中、XML数据中的可以为空的数字与.NET数字(不能为空)类型进行映射
- 结构Nullable<T>定义了一个约束:T必须是一个结构
- 定义了只读属性HasValue和Value,以及一些运算符重载。
- Nullable<T> ==> T 的运算符重载是显示定义
- T ==> Nullable<T> 的运算符重载是隐式的
Nullable<int> x; x = 4; x += 3; if (x.HasValue) { int y = x.Value; } x = null;
- 使用“?”运算符,定义可控类型的变量(int? x;)
- 可空类型可以与null比较
- 可空类型可以与算术运算符一起使用。若两个可空变量运算,任何一个值为null,则运算结果为null。
- 可空类型转换为非可空类型需进行显式转换,且使用合并运算符(??)定义一个默认值,与空值映射。
int? x1 = GetNullableType(); //GetNullableType()方法只是一个占位符,返回一个可空的int int y1 = x1 ?? 0; //x1=null时赋值是默认值0
5. 泛型方法
- 在泛型方法中,泛型类型用方法声明来定义
- 泛型方法可以在非泛型类中定义
- 所调用的泛型方法是在编译期间而非运行期间定义的,因此泛型方法可以像非泛型方法那样调用