深入理解C#(九)

*第三章(C# 2:泛型实现参数化类型)

理解泛型类型和方法

泛型方法的类型推断

例子:

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

使用编译器的类型推断:

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

类型约束

如List等,所有类型参数都可以指定为任意类型,它们未被约束。我们想制定规则,从而判断哪些是泛型类型或泛型方法呢能接受的有效类型实参。

约束来实现。约束放在泛型方法或泛型类型声明的末尾,有上下文关键字where引入。

  1. 引用类型约束

    确保使用的类型实参是引用类型:必须是第一个约束

    struct RefSample<T> where T:class

    以这种方式约束一个类型参数后,可以使用==和!=来比较引用(包括null)
  2. 值类型约束

    确保使用的类型实参是值类型:

    class ValSample<T> where T:structbr>
    设定值类型约束后,不允许使用==和!=进行比较。

    以上两种约束比较少用,后两者更有用。
  3. 构造函数类型约束

    检查类型实参是否有一个可用于创建类型实力的无参构造函数。必须是最后一个约束。

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

    在使用工厂风格的设计模式时,这个约束非常有用。
  4. 转换类型约束

    指定另一个类型,类型实参必须可以通过一致性、引用或装箱隐式地转换为该类型。还可以规定一个类型实参必须可以转换为另一个类型实参。

    这个约束意味着“在类型参数的实例上使用指定类型的成员”

实现泛型

假装T是一个真正的类型名称。额外要注意:

  1. 默认值表达式:

    例子:Dictionary<TKey,TValue>有一个TryGetValue方法:用一个输出参数来接受你打算获取的值,用boolean返回值显示是否成功。这意味着方法必须用TValue类型的值来填充输出参数。

    C# 2提供了默认值表达式(default value expression)。

    例子:以泛型方式将一个给定的值和默认值进行比较

    class DefaultValueComparison
    {
    ​ static int CompareToDefault(T value)
    ​ where T : IComparable
    ​ {
    ​ return value.CompareTo(default(T));
    ​ }

    1
    2
    3
    4
    5
    6
    7
    8
    static void Main()
    {
    Console.WriteLine(CompareToDefault("x"));
    Console.WriteLine(CompareToDefault(10));
    Console.WriteLine(CompareToDefault(0));
    Console.WriteLine(CompareToDefault(-10));
    Console.WriteLine(CompareToDefault(DateTime.MinValue));
    }

    }

这个泛型方法规定了只能使用实现了IComparable接口的类型,这样才能为传入的值调用CompareTo(T)

类型推断只能用于泛型方法,有一个泛型类型,其中不包含任何泛型方法,怎么实现类型推断?

Pair<int,string> pair=new Pair<int,string>(10,"value");
解决方法是使用包含泛型方法的非泛型辅助类。

1
2
3
4
5
6
7
8
9
public static class Pair
{
public static Pair<T1,T2> Of<T1,T2>(T1 first,T2 second)
{
return new Pair<T1,T2>(first,second);
}
}

Pair<int,string> pair=Pair.Of(10,"value");

等到第七章再回看

  1. 直接比较
    如果一个类型参数是未约束的(即没有对其应用约束),那么只能在该类型的值与null比较时才能使用==和!=操作符。不能直接比较两个T类型的值。如果一个类型参数被约束成值类型,就完全不能为它使用==和!=。如果被约束成引用类型,具体比较将完全取决于类型参数被约束成什么类型。

高级泛型

静态字段和静态构造函数

每个封闭类型都有它自己的静态字段集

同样的规则也适用于静态初始化程序和静态构造函数。

JIT(just in time即时)编译器如何处理泛型

暂略

泛型迭代

对集合执行的最常见操作之一是便利(迭代)所有元素。最简单的办法使用foreach语句。

当需要为自己的某个类型实现迭代时,由于IEnumerable扩展了旧的IEnumerable接口,所以要实现两个不同方法:

IEnumerator GetEnumerator();
IEnumerator GetRnumerator();

反射和泛型

反射的一切都是围绕“检查对象及其类型”展开的。

typeof可通过两种方式作用于泛型类型。一种方式是获取泛型类型定义,另一种方式是获取特定的已构造类型。

typeof(Dictionary<,>)或typeof(Dictionary<string,X>)

获取泛型和已构造Type对象的各种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string listTypeName = "System.Collections.Generic.List`1";

Type defByName = Type.GetType(listTypeName);

Type closedByName = Type.GetType(listTypeName + "[System.String]");
Type closedByMethod = defByName.MakeGenericType(typeof(string));
Type closedByTypeof = typeof(List<string>);

Console.WriteLine(closedByMethod == closedByName);
Console.WriteLine(closedByName == closedByTypeof);

Type defByTypeof = typeof(List<>);
Type defByMethod = closedByName.GetGenericTypeDefinition();

Console.WriteLine(defByMethod == defByName);
Console.WriteLine(defByName == defByTypeof);

反射泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void PrintTypeParameter<T>()
{
Console.WriteLine(typeof(T));
}

static void Main()
{
Type type = typeof(GenericMethodReflection);
MethodInfo definition = type.GetMethod("PrintTypeParameter");
MethodInfo constructed;
constructed = definition.MakeGenericMethod(typeof(string));
constructed.Invoke(null, null);
}
  1. 获取泛型方法定义
  2. 使用MakeGenericMethod返回一个已构造的泛型方法。
  3. 后去已构造的方法后,就可以调用了。

泛型在C#和其他语言中的一些限制

为什么不能将List转换成List?

泛型是不变的,但为什么数组是协变的?

为了支持从JAVA中编译来的代码,因为Java有协变数组。

使用泛型辅助类解决逆变性缺乏问题

缺乏操作符约束或者数值约束

解决方法:

  1. 表达式树,第九章
  2. C# 4的动态特性,14章有例子

为什么泛型只限于类型(包括类、结构、委托和接口)和方法?

  1. 缺乏泛型属性、索引器和其他成员类型

第三章小结

泛型的三个优点:

  1. 编译时的类型安全性
  2. 性能
  3. 代码的表现力
    IDE和编译器能提前验证代码

值类型性能上获益最大。在强类型的泛型API中,不再需要装箱和拆箱。
使用泛型,代码能更清楚地表达其意图。