泛型
.net泛型与C++模板
对于C++模板,用特定的类型实例化模板时,是需要模板的源代码的
泛型不仅是C#语言的一种结构,而且是CLR定义的
即使泛型类是在C#中定义的,也可以在Visual Basic中用一个特定的类型实例化该泛型
在C++中,编译器可以检测出在哪里使用了模板的某个特定类型,例如,模板B的A类型,然后编译需要的代码,来创建这个类型。而在C#中,所有操作都在运行期间进行
- 性能
var list=new ArrayList();
list.Add(44); //装箱
int i1=(int) list[0]; //拆箱
foreach (int i2 in list)
Console.WriteLine(i2); //拆箱
频繁的装拆箱对性能损失较大,遍历许多项时尤其如此
var list=new List<int>();
list.Add(44);
int i1= list[0];
foreach (int i2 in list)
Console.WriteLine(i2);
List的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作
- 类型安全
var list=new ArrayList();
list.Add(44);
list.Add(“mystring”);
list.Add(new MyClass());
for (int i in list)
Console.WriteLine(i);
可以往集合中添加任意类型,在迭代时并不是所有类型都可以转换成int,所以触发运行异常
var list=new List<int>();
list.Add(44);
list.Add(“mystring”); //编译错误
list.Add(new MyClass()); //编译错误
以上代码在编译期间就会出现错误
- 二进制代码的重用
泛型类可以定义一次,并且可以用许多不同的类型实例化,而不需要像C++模板那样访问源代码
var list=new List<int>();
list.Add(44);
var stringList=new List<string>();
stringList.Add(“mystring”);
var myClassList=new List<MyClass>();
myClassList.Add(new MyClass());
泛型类可以在一种语言中定义,在任何其他.NET语言中使用
-
代码的扩展
因为泛型类的定义放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类
但是在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类
引用类型共享同一个本地类的所有相同的实现代码
因为引用类型在实例化的泛型类中只需要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> {}
创建泛型类
泛型类的功能
泛型文档管理器示例。文档管理器用于从队列中读写文档
using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
public class DocumentManager<T>
{
private readonly Queue<T> documentQueue = new Queue<T>();
public void AddDocument(T doc)
{
lock (this)
{
documentQueue.Enqueue(doc);
}
}
public bool IsDocumentAvailable
{
get { return documentQueue.Count > 0; }
}
泛型类型使用时需注意
Class MyGenericClass<T1,T2,T3>
{
private T1 innerT1Object;
public MyGenericClass(T1 item)
{ innerT1Object=item; }
public T1 InnerT1Object
{
get
{ return innerT1Object; }
}
}
不能假定类提供了什么类型
Class MyGenericClass<T1,T2,T3>
{
private T1 innerT1Object;
public MyGenericClass(T1 item)
{ innerT1Object=new T1(); }//编译错误
public T1 InnerT1Object
{
get
{ return innerT1Object; }
}
}
我们不知道T1是什么,也就不能使用它的构造函数,它甚至
可能没有构造函数,或者没有可公共访问的默认构造函数
当比较为泛型类型提供的类型值和null时,只能使用运算符==和!=
public bool Compare(T1 op1, T1 op2)
{
if (op1 != null && op2 != null)
return true;
else
return false;
}
其中,如果T1是一个值类型,则总是假定它是非空的。于是,
在上面的代码中,Compare总是返回true。
public bool Compare(T1 op1, T1 op2)
{
if (op1 == op2) //编译错误
return true;
else
return false;
}
这段代码假定T1支持==运算符
默认值
不能将null赋予泛型类型,原因是泛型类型可以实例化为值类型,而null只能用于引用类型
为了解决这个问题,可以使用default关键字,将null赋予引用类型,0赋予值类型
在泛型中,default关键字根据泛型类型是值类型还是引用类型,default用于将泛型类型初始化为null或0
约束
如果没有使用约束,代码如下所示。
public void DisplayAllDocuments()
{
foreach (T doc in documentQueue)
{
Console.WriteLine(((IDocument)doc).Title);
}
}
问题是如果类型T没有实现IDocument接口,这个
类型强制转换就会导致一个运行异常
最好给DocumentManager添加一个约束,T必须实现IDocument接口,同时将T改为TDocument
class MyGenericClass<T> where T: constraint
{
……
}
class MyGenericClass<T> where T: constraint1,constraint2
{
……
}
class MyGenericClass<T> where T1: constraint1
where T2: constraint2
{
……
}
class MyGenericClass<T1,T2>: MyBaseClass, IMyInterface
where T1:constraint1 where T2: constraint2
{
……
}
约束必须出现在继承说明符的后面
只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
使用泛型类型还可以合并多个约束
public class MyClass<T> where T: IFoo, new()
{
……
}
如果使用new()作约束,它就必须是为类型指定的
最后一个约束
继承
泛型类型可以实现泛型接口,也可以派生自一个类
泛型类可以派生自泛型基类
public class LinkedList<T> : IEnumerable<out T>
{
……
}
public class Base<T>
{
……
}
public class Derived<T>: Base<T>
{
……
}
其要求是必须重复接口的泛型类型,或者必须指定基类的类型
public class Base<T>
{
……
}
public class Derived<T>: Base<string>
{
……
}
派生类可以是泛型类或非泛型类
public abstract class Calc<T>
{
public abstract T add(T x, T y);
public abstract T sub(T x, T y);
}
public class IntCalc: Calc<int>
{
public override int Add(int x, int y)
{ return x+y; }
public override int Sub(int x, int y)
{ return x-y; }
}
如果某个类型所继承的基类型中受到了约束,该类型就不能“解除约束”。也就是说,类型T在所继承的基类型中使用时,必须受到至少与基类型相同的约束
class SuperFarm<T>: Farm<T> where T: SuperCow
{
}
因为T在Farm中被约束为Animal,把它约束为SuperCow,就是把T约束为这些值中的一个子集,所以这段代码可以正常运行
class SuperFarm<T>: Farm<T> where T: struct
{
}
以上代码不会编译
class SuperFarm<T>: Farm<T> where T: class
{
}
编译也失败
静态成员
泛型类的静态成员只能在类的一个实例中共享
public class StaticDemo<T>
{
public static int x;
}
StaticDemo<string>.x=4;
StaticDemo<int>.x=5;
Console.WriteLine(StaticDemo<string>.x); //输出4
泛型运算符
public static implicit operator List<Animal>(Farm<T> farm)
{
List<Animal> result = new List<Animal>();
foreach (T animal in farm)
{
result.Add(animal);
}
return result;
}
public static Farm<T> operator+(Farm<T> farm1, List<T> farm2)
{
Farm<T> result = new Farm<T>();
foreach (T animal in farm1)
result.Animals.Add(animal);
foreach (T animal in farm2)
{
if (!result.Animals.Contains(animal))
result.Animals.Add(animal);
}
return result;
}
public static Farm<T> operator +(List<T> farm1, Farm<T> farm2)
{
return farm2 + farm1;
}
Farm<Animal> newFarm = farm + dairyFarm;
//在这行代码中,Farm<Cow>实例dairyFarm隐式转换为List<Animal>,大家可能认为下面的代码也可以做到
/*
public static Farm<T> operator +(Farm<T> farm1, Farm<T> farm2)
{
Farm<T> result = new Farm<T>();
foreach (T t in farm1) result.Animals.Add(t);
foreach (T t in farm2)
if (!result.Animals.Contains(t))
result.Animals.Add(t);
return result;
}
*/
//但是Farm<Cow>不能转换为Farm<Animal>,所以会失败。如果有下列代码就可以解决该问题
/*
public static implicit operator Farm<Animal>(Farm<T> farm)
{
Farm<Animal> result = new Farm<Animal>();
foreach (T t in farm)
if (t is Animal)
result.Animals.Add(t);
return result;
}
*/
泛型接口
协变和抗变
协变和抗变指对参数和返回值的类型进行转换
.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape
public void Display(Shape o) { }
现在可以传递派生自Shape基类的任意对象
Rectangle r=new Rectangle { Width=5, Height=2.5 };
Display(r);
方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来可行。
public Rectangle GetRectangle() { …… }
Shape s=GetRectangle();
在C# 4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变
多态性不适用于接口
Cow myCow=new Cow(“milkCow1”);
Animal myAnimal=myCow;
IMethaneProducer<Cow> cowMathaneProducer=myCow;
//下列代码不能通过编译
IMethaneAnimal<Animal> animalMathaneProducer=cowMathaneProducer;
泛型接口的协变
如果泛型类型用out关键字标注,泛型接口就是协变的
对于接口定义,协变类型参数只能用作方法的返回值或属性get访问器
public interface IIndex<out T>
{
T this [int index] { get; }
int Count { get; }
}
泛型接口的抗变
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样接口只能把泛型类型T用作其方法的输入,不能用作返回类型
public interface IDisplay<in T>
{
void Show (T item);
}
ShapeDisplay类实现IDisplay,并使用Shape对象作为输入参数
public class ShapeDisplay : IDisplay<Shape>
{
public void Show(Shape s)
{
Console.WriteLine("{0} Width: {1}, Height: {2}",
s.GetType().Name, s.Width, s.Height);
}
}
创建ShapeDisplay类的一个新实例,会返回IDisplay,并把它赋予shapeDisplay变量。
因为IDisplay是抗变的,所以可以把结果赋予IDisplay,其中Rectangle派生自Shape,这次接口的方法只能把泛型类型定义为输入,而Rectangle满足Shape的所有要求
IDisplay<Shape> shapeDisplay = new ShapeDisplay();
IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
rectangleDisplay.Show(rectangles[0]);
泛型结构
与类相似,结构也可以是泛型的,只是没有继承特性
Int32是一个结构,而结构的实现同值类型,所以结构不能为空。这个问题不仅存在于数据库中,也存在于把XML类型映射为.NET类型
一种解决方案是数据库和XML文件中的数字映射为引用类型,因为引用类型可以为空值,但也会在运行期间带来额外的开销
使用Nullable很容易解决这个问题
定义Nullable的一个简化版本
类的对象可以为空,所以对类使用Nullable类型是没有意义的
因为可空类型使用得非常频繁,C#有一种特殊的语法
Nullable<int> x1;
x1=null; 等价于 x1=new Nullable<int>();
int? x2;
可空类型可以与null和数字比较
int? x3 = null;
if (x3 == null)
Console.WriteLine("x is null");
else if (x3 < 0)
Console.WriteLine("x is smaller than 0");
if (x3.HasValue)
{
……
}
这不适用于引用类型,即使引用类型有一个HasValue属性,也不能用这种方法。因为引用类型的变量值为空,就表示不存在这个对象,当然不能通过对象来访问属性,否则会抛出异常
使用Value属性可以查看可空类型的值。但如果HasValue是false,访问Value属性就会抛出异常InvalidOperationException
int? op1=5;
int? result=op1*2;
//下面的代码不会被编译:
int? op1=5;
int result=op1*2;
//为了使上面的代码正常工作,必须进行显式转换:
int? op1=5;
int result=(int)op1*2;
//或者通过Value属性访问值:如果op1为null,会引发异常
int? op1=5;
int result=op1.Value*2;
可空类型还可以与算术运算符一起使用
int? y1 = 2;
int? y2 = null;
int? y3 = y1 + y2;
只要两个可空变量中的任一个的值是null,它们的和就是null
非可空类型可以转换为可空类型,在不需要强制类型转换的地方可以进行隐式转换,这种转换总是成功的
int y1 = 4;
int? x1=y1;
从可空类型转换为非可空类型可能会失败,如果可空类型的值是null,并且把null值赋予非可空类型,就会抛出InvalidOperationException异常
int? x1 = null;
int y1=(int)x1;
如果不进行显式类型转换,还可以使用合并运算符从可空类型转换为非可空类型
int? x1 = null;
int y1= x1 ?? 0;
下面两个表达式的结果是相同的:
op1 ?? op2
op1==null? op2:op1
泛型方法
void Swap<T>(ref T x, ref T y)
{
T temp;
temp=x;
x=y;
y=temp;
}
int i=4, j=5;
Swap<int>(ref i, ref j);
因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用
int i=4, j=5;
Swap(ref i, ref j);
如果类是泛型的,就必须为泛型方法类型使用不同的标识符
public class Defaulter<T>
{
public T GetDefault<T>()
{
return default(T);
}
}
以上代码不会编译
泛型方法规范
泛型方法可以重载,为特定的类型定义规范
public class MethodOverloads {
public void Foo<T>(T obj) {
Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
}
public void Foo(int x) {
Console.WriteLine("Foo(int x)");
}
public void Bar<T>(T obj) {
Foo(obj);
}
}
在编译期间,会使用最佳匹配。如果传递了一个int,就选择带int参数的方法;对于其他参数类型,编译器会选择方法的泛型版本
所调用的方法是在编译期间定义的,而不是运行期间
Bar()方法选择了泛型Foo()方法,而不是用int参数重载的Foo()方法,原因是编译器在编译期间选择Bar()方法调用的Foo()方法。由于Bar()方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用了Foo()方法,在运行期间给Bar()传递一个int值不会改变这一点
System.Collections.Generic名称空间
Dictionary<K,V>
这个类型可以定义键/值对的集合
每一项的键都必须是唯一的
Dictionary<string,int> things= new Dictionary<string,int>();
Things.Add(“Green things”, 29);
Things.Add(“Blue things”, 94);
Things.Add(“Yellow things”, 36);
Things.Add(“Red things”, 58);
Things.Add(“Brown things”,87);
foreach (string key in things.Keys)
Console.WriteLine(key);
foreach (int value in things.Values)
Console.WriteLine(value);
foreach (KeyValuePair<string,int> thing in things)
Console.WritleLine(“{0}={1}”,thing.Key,thing.Value);
定义泛型委托
public delegate T1 MyDelegate<T1,T2>(T2 op1, T2 op2)
where T1:T2;