第12章 泛型
CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。
泛型的主要好处是减少代码量,减少类型转换次数,提高代码性能。
12.3 泛型基础结构
所有引用类型的实参或变量实际只是指向堆上的对象的指针,在32位windows系统上,全部为32位指针;在64位windows系统上,全部为64位指针。
12.6 委托和接口的逆变和协变泛型类型实参
泛型类型中只有委托跟接口有协变与逆变。
不变量(invariant) 意味着泛型类型参数不能更改。
逆变量(contravariant) 意味着泛型类型参数可以从一个基类更改为该类的派生类,在C#中,用in关键字标记你变量形式的泛型类型参数。逆变量泛型参数只出现在输入位置。
协变量(covariant)意味着泛型类型参数可以从一个派生类更改为它的基类。在C#中,是用out关键字标记协变量形式的泛型类型参数。协变量类型参数只能出现在输出位置。
namespace GenericTypeApp
{
class Program
{
static void Main(string[] args)
{
GenericClass<Class2, Class2> cal1 = new TheClass<Class2, Class2>();
//out 协变是从基类转到派生类 in 逆变是从派生类到基类
GenericClass<Class1, Class3> cal2 = cal1;
cal2.NoneGenericMethod(new Class3());
}
}
interface GenericClass<out T1, in T2> where T2 : T1
{
//协变参数必须是输出值,抗变类型必须是输入参数
T1 NoneGenericMethod(T2 p2);
}
class TheClass<T1, T2> : GenericClass<T1, T2> where T2 : T1
{
public T1 NoneGenericMethod(T2 p2)
{
return p2;
}
}
class Class1 { }
class Class2 : Class1 { }
class Class3 : Class2 { }
}
12.7 泛型方法
C#编译器支持在调用一个泛型方法时进行类型推断(type inference)。
执行类型推断时,C#使用变量的数据类型,而不是由变量引用的对象的实际类型。
using System;
namespace GenericTypeApp
{
class Program
{
static void Main(string[] args)
{
Print(100.0); //Print<T>(T p)
Print(100); //Print(int p)
}
public static void Print<T>(T p)
{
Console.WriteLine("Print<T>(T p)");
}
public static void Print(int p)
{
Console.WriteLine("Print(int p)");
}
}
}
12.9 可验证性和约束
interface Interface1<T>
{
void Method2<T2>(T2 t) where T2 : IEnumerable<T2>;
}
class Class1<T>
{
public virtual int Method1<T1>(T1 t1, T1 t2) where T1:IComparable<T1>
{
return t1.CompareTo(t2);
}
}
class Class2<T> : Class1<T>, Interface1<T>
{
public override int Method1<T1>(T1 t1, T1 t2)//不用声明约束,会直接从基类方法继承约束
{
return t2.CompareTo(t1);
}
public void Method2<T2>(T2 t) where T2 : IEnumerable<T2>
{
foreach (var i in t)
{
Console.WriteLine(i.ToString());
}
}
}
重写一个虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。事实上,根本不允许重写方法的类型参数指定任何约束。但是类型参数的名字是可以改变的。实现接口方法时,方法必须指定约束。
12.9.1 主要约束
class Class3
{
public void Method1<T>(T t) where T : Stream
{
t.Close();
}
public void Method2<T>(T t) where T : class
{
t = null;
}
public void Method3<T>(T t) where T : struct
{
t = new T();
}
}
12.9.2 次要约束
一个类型参数可以指定零个或者多个次要约束,次要约束代表的是一个接口类型。指定一个接口类型约束时,是向编译器承诺指定的类型必须实现了所有接口约束。
interface Interface1 { }
interface Interface2 { }
class BaseClass { }
class MyClass<T> where T : BaseClass, Interface1, Interface2 { }
12.9.3 构造器约束
一个类型参数可以指定零个或者一个构造器约束。指定构造器约束相当月向编译器承诺一个指定的类型实参是实现了公共无参构造器的一个非抽象类型。如果同时制定了构造器约束和struct约束,C#编译器会认为这是一个错误。
internal class Class4<T> where T : new() { }
目前CLR以及C#编译器只支持无参构造器。
12.9.4其他可验证性问题
1. 泛型类型变量的转型
public void Method3<T>(T t)
{
string s = t as string;
int y = (int)(object)t;
string z = (string)(object)t;
}
2. 将一个泛型类型变量设为默认值
public void Method2<T>(T t)
{
t = default(T);
}