1、什么是泛型?
自从C#2.0开始支持泛型。
1. 作用:
- 可以创建独立于被包含类型类和方法。
- 不必给不同的类型编写功能相同的方法或类,只需要传入一个通用数据类型,就可以合并代码。
- 可以定义类型安全类,而不会损害类型安全、性能或工作效率。
- 只须一次性地将服务器实现为一般服务器,同时可以用任何类型来声明和使用它。为此,需要使用 < 和 > 括号,以便将一般类型参数括起来。
2、泛型的优缺点
- 性能:把值类型转换为引用类型,要进行装箱操作,反之,则要使用拆箱操作,这样就会损失性能。例如
List<int>
,将泛型类型定义成int
,在使用int
类型的时候,就不必再进行装箱和拆箱操作。类型安全:实例化了一个类型的栈,就不能处理其他类型的数据。
var list = new List<int>; list.Add(23); list.Add("test"); //compile time error list.Add(new TestClass()); //compile time error
二进制代码重用:泛型类定义一次,可以用许多不同的类型实例化。
var list = new List<int>; var stringList = new List<string>; var myClassList = new List<MyClass>;
代码扩展
- 命名约定:
- 泛型类型的名称用字母T作为前缀;
- 如果没有特殊要求,泛型类允许用任意类替代,且只使用了一个泛型类型,就可以用T作为泛型类型的名称;
- 如果有特定的要求(例如:实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性语言。
3、泛型类的默认值是什么?
- 使用default关键字。
- 泛型类型既可以实例化为值类型也可以实例化引用类型,
- 将null赋予引用类型,将0赋予值类型。
//将类型T指定为null public T GetDocument() { T doc = default(T); lock (this) { doc = documentQueue.Dequeue(); } return doc; }
4、泛型约束定义
- 约束是使用 where 上下文关键字指定的。
- 在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。
- 如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。
- 这些限制称为约束。
5、泛型约束的种类
种类 说明 T:结构 类型参数必须是值类型。可以指定除Nullable以外的任意值类型 T:类 类型参数必须是引用类型,包括任何类、接口、委托或数组类型 T:new() 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new()约束必须最后指定 T:<基类名> 类型参数必须是指定的基类或派生自指定的基类 T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束,约束接口也可以是泛型的 T:U 为T提供的类型参数必须是为U提供的参数或派生自为U提供的参数。这种称为裸类型约束
6、接口约束
声明一个泛型类MyClass,这样,类型参数T就可以实现IComparable接口。
public class MyClass<T> where T:IComparable{}
7、基类约束
- 指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数,
- 这样的约束必须出现在所有约束之前。
class MyClass<T,U> where T: class where U: struct {}
8、构造函数约束
- 使用new运算符创建类型参数,但类型参数必须受构造函数约束,即为new()的约束。
- new()约束可以让编译器知道:提供的任何参数类型都必须具有可访问的无参数的(或默认)的构造函数,
- new约束放在where语句的最后。
public class MyClass<T> where T: IComparable, new() { T item = new T(); }
9、对于多个类型参数,每个类型参数都使用一个where子句
interface ITest {}
class Dictiongary<TKey, TValue>
where TKey: IComparable, IEnumerable
where TValue: ITest
{
public void Add(TKey key, TValue value)
{}
}
10、将约束附加到泛型方法的类型参数
public bool MyMethod<T>(T t) where T:IMyInterface{}
11、裸类型约束(不理解)
当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用。
class List<T> { void Add<U>(List<U> items) where U: T {} }
12、泛型类的继承
- 泛型类型可以实现泛型接口,也可以派生自一个类,
- 但是要求必须重复接口的泛型类型,或者必须指定基类的类型。
public class MyClass<T>: Base<T>{} public class MyClass<T>: Base<int>{}
13、泛型接口定义
//定义接口
public interface ITest<T>
{
T GetTestName(T t);
}
//实现接口
public class MyClass:ITest<int>
{
public int GetTestName(int temp){}
}
14、泛型的协变和逆变(抗变,反变)
- 协变:子类型的对象引用到父类型的对象引用的转化,使用关键词out;
将IDemo类型转换为IDemo类型(子->父)
//用于输出的协变 interface IDemo<out T> { T Method(object value); } public class Demo:IDemo<string> { public string Method(object value) { return value.ToString(); } } //在main函数中的使用 IDemo<string> demoStr = new Demo(); IDemo<object> demoObj = demoStr;
- 逆变:父类型的对象引用到子类型的对象引用的转化,使用关键词in;
将IDemo类型转换为IDemo类型(父->子)
//用于输入的逆变 interface IDemo<in T> { T Method(object value); } public class Demo:IDemo<string> { public string Method(object value) { return value.ToString(); } } //在main函数中的使用 IDemo<object> demoObj = new Demo(); IDemo<string> demoStr = demoObj;
15、泛型结构
- Nullable,是由 .NET Framework定义的结构。
- 该泛型结构是可空类型的。
- C#有一种特殊的语法,用于定义可空类型的变量,使用”?”运算符。
//x1和x2都是可空的int类型 Nullable<int> x1; int? x2;
16、非可空类型与可空类型之间的转换
非可空类型-可空类型(隐式转换)
int y = 4; int? x = y;
可空类型-非可空类型
1、这种转换可能会失败。
- 如果可空类型是null,而把null值赋予非可空类型,就会抛出异常。
- 此时,就需要类型强制转换运算符进行显式转换。
int? x = GetNullableType(); int y = (int)x;
2、如果不进行显式类型转换,还可以使用合并运算符从可空类型转换为非可空类型。
- 合并运算符的语法是”??”,为转换定义了一个默认值,以防止可空类型的值是null。
int? x = GetNullableType(); int y = x ?? 0;
17、泛型方法(详见第八章)
泛型方法可以在非泛型类中定义
void swap<T>(T x, T y) {...}