《深入理解C#》泛型高级

1.泛型约束

约束要放到泛型方法或泛型类型声明的末尾,用where来引入。

1.1.引用类型约束

表示成 T:Class,确保为引用类型,例如:

struct RefSample<T> where T : class

允许使用==和!=来比较引用(包括null)。

1.2.值类型约束

表示成 T:struct,却表类型参数为值类型,包括枚举(enums)。

class ValSample<T> where T : struct

如果一个类型参数存在多个约束,值约束必须为第1个指定。不允许使用==和!=来比较。

1.3.构造函数类型约束

​ 构造函数类型约束表示成T : new(),必须是所有类型参数的最后一个约束,它检查类型实参是否有一个可用于创建类型实例的无参构造函数。这适用于所有值类型;所有没有显式声明构造函数的非静态、非抽象类;所有显式声明了一个公共无参数构造函数的非抽象类。

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

1.4.转换类型约束

​ 最后(也是最复杂的)一种约束允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。你还可以规定一个类型实参必须可以转换为另一个类型实参——这称为类型参数约束(type param eter constraint);

例子:

声 明 已构造类型的例子
class Sample<T> where T : Stream 有效: Sample<Stream> (一致性转换)
无效: Sample<string>
struct Sample<T> where T : IDisposable 有效: Sample<SqlConnection> (引用转换)
无效: Sample<StringBuilder>
class Sample<T> where T : IComparable<T> 有效: Sample<int> (装箱转换)
无效: Sample<FileInfo>
class Sample<T,U> where T : U 有效: Sample<Stream,IDisposable> (引用转换)
无效: Sample<string,IDisposable>

可以指定多个接口,但只能指定一个类。例如,以下声明毫无问题(尽管很难满足):

//success, 可以指定多个接口,但只能指定一个类
class Sample<T> where T : Stream,
                          IEnumerable<string>,
                          IComparable<int> 

但以下声明就有问题了:

//faild, 可以指定多个接口,但只能指定一个类
class Sample<T> where T : Stream,
                          ArrayList,
                          IComparable<int>

1.5.组合约束

  • 不能既是引用类型又是值类型
  • 每一个值类型都有一个构造函数,就不允许再指定一个构造函数约束
  • 多个转换类型时,并且其中一个为类,那它应该出现在接口前面,而我们不能多次指定 同一个接口
  • 不同类型参数可以有不同约束,它们分别由一个where引入

来看一些有效和无效的例子。

有效:

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

无效:

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	//U可能是object,但不好
class Sample<T,U> where T : Stream, U : IDisposable	//缺少where

总结:

  • 值类型在最前,引用类型第二,然后是其他约束,new()构造函数约束必须在最后
  • 同一个接口只能约束一次

2.实现泛型

2.1.默认值表达式 default(T)

​ 如果已经明确了要处理的类型,也就知道了它的“默认”值,例如未初始化字段的默认值。不知道要引用的类型,就不能直接指定默认值。你不能使用null,因为它可能不是一个引用类型。也不能使用0,因为它可能不是数值类型。

​ 为了满足这方面的需求,C# 2提供了默认值表达式(default value expression)。虽然C#语言规范没有说它是一个操作符,但可以把它看做是与typeof相似的操作符,只是返回值不同。代码清单3-4在一个泛型方法中对此进行了演示,并给出了“类型推断”和“转换类型约束”的实例。

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

static int CompareToDefault<T>(T value)
    where T : IComparable<T>
{
    return value.CompareTo(default(T));
}
...
Console.WriteLine(CompareToDefault("x"));
Console.WriteLine(CompareToDefault(10));
Console.WriteLine(CompareToDefault(0));
Console.WriteLine(CompareToDefault(-10));
Console.WriteLine(CompareToDefault(DateTime.MinValue));

2.2.直接比较

泛型比较接口

共有4个主要的泛型接口可用于比较。
IComparer<T>IComparable<T>用于排序(判断某个值是小于、等于还是大于另一个值),而IEqualityComparer<T>IEquatable<T>通过某种标准来比较两个项的相等性,或查找某个项的散列(通过与相等性概念匹配的方式)。

​ 如果换一种方式来划分这4个接口,IComparaer<T>IEqualityComparer<T>的实例能够比较两个不同的值,而IComparable<T>IEquatable<T>的实例则可以比较它们本身和其他值。

2.3.完整的比较例子:表示一对值

public sealed class Pair<T1, T2> : IEquatable<Pair<T1, T2>>
{
    private static readonly IEqualityComparer<T1> FirstComparer = EqualityComparer<T1>.Default;
    private static readonly IEqualityComparer<T2> SecondComparer = EqualityComparer<T2>.Default;

    private readonly T1 first;
    private readonly T2 second;

    public Pair(T1 first, T2 second)
    {
        this.first = first;
        this.second = second;
    }

    public T1 First { get { return first; } }

    public T2 Second { get { return second; } }

    public bool Equals(Pair<T1, T2> other)
    {
        return other != null &&
        FirstComparer.Equals(this.First, other.First) &&
        SecondComparer.Equals(this.Second, other.Second);
    }
    public override bool Equals(object o)
    {
        return Equals(o as Pair<T1, T2>);
    }
    public override int GetHashCode()
    {
        return FirstComparer.GetHashCode(first) * 37 +
            SecondComparer.GetHashCode(second);
    }
}

想要创建实例,

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

这不是很理想,我们想要的使用类型推断

类型推断只能用于泛型方法,而且Pair类不包含任何泛型方法。如果我们在泛型类型中放入一个泛型方法,那么在调用该方法时仍然需要指定该类型的类型参数。解决方法是使用包含泛型方法的非泛型辅助类

解决方法是使用包含泛型方法的非泛型辅助类

public static class Pair
{
    public static Pair<T1, T2> New<T1, T2>(T1 first, T2 second)
    {
        return new Pair<T1, T2>(first, second);
    }
}

这样创建更加优雅

 Pair<int, string> pair2 =Pair.New(10,"value");

3.泛型在C#与其他语言中的限制

1.泛型没有可变性(协变、逆变)

  左侧的数组版本虽然可以通过编译,但执行时一样会失败。泛型的设计者认为,这比编译时就失败还要糟糕——静态类型的全部意义就在于在代码运行之前找出错误。

posted @ 2021-03-07 20:34  【唐】三三  阅读(246)  评论(0编辑  收藏  举报