C#高级程序设计(三)——泛型

在C#引入泛型之前,如果一个类或者一个方法想要支持多种类型,它就不得不把相应的参数或者返回值定义为object类型,这就意味着代码在执行的过程中需要很多的转型,并不是说你的代码一定不要用到转型,但是转型确实会带来很多潜在的问题,因为它将本该属于编译时的类型检查延迟到了运行时,而且也会带来一定的性能问题(装箱和拆箱)。

C#2引入了泛型,包括两种形式的泛型:泛型类型和泛型方法。下面说一下和泛型有关的一些特性:

一、泛型约束

C#泛型支持四种类型的约束,但是它们使用同样的语法:

引用类型约束

对于引用类型的约束,泛型对象之间可以通过==和!=进行比较,但是需要注意的是,除非指定了其它约束,否则只会比较引用本身(即使引用对象类型重载了比较方法),使用转换类型约束,重载的比较方法才会被编译器调用。

class RefSample<T> where T : class


值类型约束

对于值类型的约束,泛型对象之间不允许使用==和!=进行比较。

class ValSample<T> where T : struct


构造函数类型约束

构造函数类型约束类型要包含无参构造函数。这样就可以构造泛型对象实例。

public T CreateInstance<T>() where T : new()
{
return new T();
}

转型类型约束

转型类型约束允许你指明一种类型,使得参数类型必须能被隐式转换成指明的类型。

转换类型约束非常重要,因为你可以用泛型对象调用指明类型的方法。例如,对于T : IComparable<T>,这意味着你能直接比较两个T类型的对象。

class Sample<T> where T : Stream,
IEnumerable<string>,
IComparable<int>

当几种约束混合使用的时候,实际上还是有一些规则的,

1)没有一个类型可以既是引用类型又是值类型,所以同时做引用类型约束和值类型约束是不允许的。

2)每一个值类型都有一个无参构造函数,所以不能同时有值类型约束和构造函数类型约束。

3)对于多个转换类型约束,最多只能有一个类类型,而且类类型必须放在接口类型的前边。而且每一个类型转换约束只能出现一次。

4)不同的参数类型可以有不同的约束,它们通过各自的where来指定。

下面列出了一些合法与不合法的约束混合使用:

Valid
class Sample<T> where T : class, IDisposable, new()
class Sample<T> where T : struct, IDisposable
class Sample<T,U> where T : class where U : struct, T
class Sample<T,U> where T : Stream where U : IDisposable
Invalid
class Sample<T> where T : class, struct
class Sample<T> where T : Stream, class
class Sample<T> where T : new(), Stream
class Sample<T> where T : IDisposable, Stream
class Sample<T> where T : XmlReader, IComparable, IComparable
class Sample<T,U> where T : struct where U : class, T
class Sample<T,U> where T : Stream, U : IDisposable

二、类型推断

C#编译器允许在调用泛型方法的时候不用显式指明参数类型,而是通过方法中传递的参数(注意不是返回值类型)进行类型推断,注意的是这只针对泛型方法有用(对泛型类型无效),例如:

static List<T> MakeList<T>(T first, T second)
...
List<string> list = MakeList<string>("Line 1", "Line 2");

对于这个泛型方法,编译器可以根据参数的类型推断出T,因此允许用户无需显式指明类型:

List<string> list = MakeList("Line 1", "Line 2");

三、默认值表达式

很多时候我们需要在泛型中使用默认值,C#使用default(T) 表达式返回当前泛型类型的默认值。

四、泛型中的比较

如果参数类型没有被约束,你只能使用==/!=运算符将值和null进行比较,不能用这两个操作符比较两个T类型的值。

如果参数类型被约束为值类型,那么==/!=根本就不能被应用。

如果参数类型被约束为引用类型,那么==/!=只会比较两者的引用。

如果参数类型被约束为为继承自重载了==/!=的类或接口,那么将使用重载的==/!=进行比较

例如:

static bool AreReferencesEqual<T>(T first, T second) where T : class
        {
            return first == second;
        }

string str = "World";
            string str1 = "Hello" + str;
            string str2 = "Hello" + str;

            Console.WriteLine(str1 == str2);
            Console.WriteLine(AreReferencesEqual(str1, str2)); 

Output:
True
False

五、泛型中的静态字段与静态构造函数

对于泛型类型中的静态字段,它的每一个实例化类型都会拥有一份独立的静态字段。

class TypeWithField<T>
{
public static string field;
public static void PrintField()
{
Console.WriteLine(field + ": " + typeof(T).Name);
}
}
...
TypeWithField<int>.field = "First";
TypeWithField<string>.field = "Second";
TypeWithField<DateTime>.field = "Third";
TypeWithField<int>.PrintField();
TypeWithField<string>.PrintField();
TypeWithField<DateTime>.PrintField();

Output:
First: Int32
Second: String
Third: DateTime

所以一个通用的规则是:“one static field per closed type.”。这对静态构造函数同样适用,

六、JIT编译器怎样处理泛型

对于泛型类型,JIT会为每一个closed type创建它的本地代码,每一个closed type拥有自己的静态成员,而对于泛型方法或者泛型类型中的方法,JIT会对每一个值类型closed type创建不同的native code,但是对于所有引用类型closed type它们会共享同样的native code,其原因在于所有的引用都用同样大小的,所以其对象寻址方式是相同的。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值