C#高编 - 泛型
1.性能:
泛型的一个主要优点是性能,这是泛型比集合优越的原因。System.Collections.Generic名称空间中的List<T>类不使用对象,而是在使用时定义类型。
使用ArrayList类,在存储对象的时候,执行装箱操作,而在读取值的时候,进行拆箱。如:
var list = new ArrayList(); list.Add(44);//boxing-convert a value type to a reference type int i1 = (int)list[0];//unboxing-convert a reference type to a value type foreach(int i2 in list) { Console.WriteLine(i2); }
对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换成值类型时,需要进行装箱和拆箱。
装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。
2.类型安全:
泛型的另一个特征是类型安全,在泛型类List<T>中,泛型类型T定义了允许使用的类型。而ArrayList可以存储不同的对象,在遍历时容易出错。
3.二进制代码的重用:
泛型允许更好地重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。泛型类型可以在一种语言中定义,在任何其它.NET语言中使用。
4.命名约定:
命名规则:
- 泛型类型的名称用字母T作为前缀。
- 如果没有特殊的要求,泛型类型允许用任意类代替,且只使用了一个泛型类型。就可以用字符T作为泛型类型的名称。
-
public class List<T> { } public class LinkedList<T> { }
- 如果泛型类型有特定的要求(例如,它必须实现一个借口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称。
-
public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e); public delegate TOutput Converter<TInput, TOutput>(TInput from); public class SortedList<TKey, TValue> { }
5.创建泛型类:
泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。
public class LinkedListNode { public LinkedListNode(object value) { this.Value = value; } public object Value { get; private set; } public LinkedListNode Next { get; internal set; } public LinkedListNode Prev { get; internal set; } } //创建泛型版本,用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedListNode<T>。 public class LinkedListNode<T> { public LinkedListNode(T value) { this.Value = value; } public T Value { get; private set; } public LinkedListNode<T> Next { get; internal set; } public LinkedListNode<T> Prev { get; internal set; } }
每个处理对象类型的类都可以有泛型实现方式。另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作。
6.泛型类的功能:
不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。可以使用default关键字,通过default关键字,将null赋予引用类型,将0赋予值类型。
public T GetDocument() { T doc = default(T);//将null赋予引用类型,将0赋予值类型 lock(this) { doc = documentQueue.Dequeue(); } return doc; }
7.约束:
用法一:约束泛型类型必须实现接口,这样在遍历派生类时可以通过接口声明使用基础属性
public class DocumentManager<TDocument> where TDocument : IDocument
泛型支持的约束类型
约束 | 说明 |
where T:struct | 对于结构约束,类型T必须是值类型 |
where T:class | 类约束指定类型T必须是引用类型 |
where T:IFoo | 指定类型T必须实现接口IFoo |
where T:Foo | 指定类型T必须派生自基类Foo |
where T:new() | 这是一个构造函数约束,指定类型T必须有一个默认构造函数 |
where T1:T2 | 这个约束也可以指定,类型T1派生自泛型类型T2。该约束也成为裸类型约束 |
注:只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
- 使用泛型类型还可以合并多个约束。如:where T:IFoo,new()
- where子句中,不能定义必须由泛型类型实现的运算符。
8:继承:
泛型类可以派生自泛型基类:其要求是必须重复接口的泛型类型,或者必须指定基类的类型。
public class Derived<T> : Base<T> public class Derived<T> : Base<string>
派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类型实现。
public abstract class Calc<T> { public abstract T Add(T x,T y); } public class IntCalc:Calc<Int> { public override int Add(int x,int y) { return x + y; } }
9:静态成员:
泛型类的静态成员只能在类的一个实例中共享。
10:协变和抗变:(需要深入学习)
在NET4之前,泛型接口是不变的。NET4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展。
协变和抗变指对参数和返回值的类型进行转换。
在.NET中,参数类型是协变的。如:方法的传入参数若为基类,则也可以用其派生类传递。
public void Display(shape o){} Rectangle r = new Rectangle{width = 5,Height = 2.5} Display(r);
方法的返回类型是抗变的。如:方法返回类型是基类,不能将其赋予其派生类,反之可以。
public Rectangle GetRectangle(); Shape s = GetRectangle();
如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。
public interface IIndex<out T> { T this[int index] {get;} int count{get;} }
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入。
public interface IDisplay<int T> { void Show(T item); }
11.泛型结构:
泛型结构Nullable<T>,由.NET Framework定义。
解决问题:数据库中的数字和编程语言中的数字有显著不同的特征,因为数据库中的数字可以为空,而C#的数字不能为空。这个问题不仅存在数据库,也存在XML数据映射中。
使用"?"可空运算符:用于定义可空类型的变量,下面的例子都是可空int类型的实例:
Nullable<int> x1; int? x2;
可空类型可以与null和数字比较。可空类型还可以与算术运算符一起使用,当对两个可空变量进行加和,如果其中一个是null,则他们的和就是null。
非可空类型可以转换为可空类型,在不需要强制类型转换的地方可以进行隐式转换,这种转换总是成功的:
int y1 = 4; int? x1 = y1;
但从可空转换为非可空类型时需要强制转换运算符进行显式转换,否则当可空类型的值是null,并且把null值赋予非可空类型,会抛出异常。
int? x1 = GetNullableType(); int y1 = (int)x1;
使用"??"合并运算符:从可空转换为非可空类型如果不进行显式类型转换,可以使用合并运算符,为转换定义了一个默认值,以防可控类型的值是null.
int? x1 = GetNullableType(); int y1 = x1 ?? 0;
12.泛型方法:
泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。
void Swap<T>(ref T x, ref T y) { T temp; temp = x; x = y; y = temp; }
因为编译器会通过调用Swap方法来获取参数类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:
int i = 4; int j = 5; //Swap<int>(ref i, ref j); Swap(ref i, ref j);
带约束的泛型方法:where子句不仅可以用于泛型类同时也可用于泛型方法
public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source) where TAccount:IAccount { decimal sum = 0; foreach(TAccount a in source) { sum += a.Balance; } return sum; } //调用 decimal amount = Algorithm.Accumulate<Account>(accounts); //或者 decimal amount = Algorithm.Accumulate(accounts);
带委托的泛型方法:
public static T2 Accumulate<T1,T2>(IEnumerable<T1> source,Func<T1,T2,T2> action) { T2 sum = default(T2); foreach(T1 item in source) { sum = action(item,sum); } return sum; } //调用时需要知道泛型参数类型,因为编译器不能自动推断出该类型。 deciaml amount = Algorithm.Accumulate<Account,decimal>(amounts,(item,sum) => sum += item.Balance);
泛型方法规范:
泛型方法可以重载,为特定的类型定义规范。这也适用于带泛型参数的方法,在编译期间,会使用最佳匹配。
//如果传递int,就选择带int参数的方法,对于其它参数类型,编译器会选择方法的泛型版本 public void Foo<T>(T obj) {...} public void Foo(int x) {...}
需要注意的是,所调用的方法是在编译期间定义的,而不是在运行期间。举例说明:
public class MethodOverloads { public void Bar<T>(T obj) { Foo(obj); } //调用时,Bar方法选择了泛型的Foo方法,原因是编译器在编译期间选择Bar方法调用的Foo方法,由于Bar方法定义了一个泛型参数,而且泛型Foo方法匹配这个类型,就算在运行期间给Bar方法传递一个int值也不会改变这点。 static void Main() { var test = new MethodOverloads(); test.Bar(44); }