zoukankan      html  css  js  c++  java
  • 《深入理解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.泛型没有可变性(协变、逆变)

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

  • 相关阅读:
    怎么使用 Jupyter Notebook 进入指定的目录
    安卓手机安装Xposed框架/钉钉虚拟定位
    Some of the Example google dorks 2019谷歌hacker语法检索表
    在心脏内部,普通的WEB用户会怎样? 心脏滴血漏洞分析学习
    Windows的安全配置基础,打造一个安全点的Windows
    计算机存储单位
    Python如何安装whl文件,python安装py包
    jdk文件中javaw的作用
    msfconsole基础使用,简洁高效学习版
    VirtualBox报错:不能为虚拟电脑XXX打开一个新任务
  • 原文地址:https://www.cnblogs.com/tangge/p/14496225.html
Copyright © 2011-2022 走看看