zoukankan      html  css  js  c++  java
  • 自引用泛型模式分析

    曾经有人问我这样一个问题:如何迫使子类提供无参构造函数。当时给出的答案是让子类实现这样一个接口。

        public interface IMustHaveParameterLessConstructor<T>
            where T : IMustHaveParameterLessConstructor<T>, new()
        {
        }

    这种在泛型参数中引用自身的技法,还有个名字,叫做“Self-Referencing Generics”模式。这个技法在C++中已经被使用了20多年,只不过叫做Curiously Recurring Template

    这个技法可以用来实现不少有用的功能。比如为所有子类实现Singleton模式

         public class Singleton<T>
            where T : new()
        {
            private static readonly T instance = new T();
            public static T Instance
            {
                get { return instance; }
            }
        }
    
        public class Model : Singleton<Model>
        {
        }

    下面谈谈这个技法的劣势。

    首先,它影响代码的可读性,比如说用到这种程度的时候。

        public interface IComponent<T>
        { }
    
        public interface IComponentProvider<TComponent>
            where TComponent : IComponent<IComponentProvider<TComponent>>
        { }
    
        public interface IComponentProviderWorkaround<TComponent, TSelf>
            where TComponent : IComponent<TSelf>
            where TSelf : IComponentProviderWorkaround<TComponent, TSelf>
        { }

    这就是自找麻烦了。别人读起来也会想骂人。

    其次,这个技法其实是反面向对象的。如果你的类继承层次多于一层,就会产生问题。 

    Eric Lippert在其博文《Curiouser and curiouser》中从继承关系的逻辑合理性的角度进行了分析。本质上讲,自引用泛型违反了里氏替换原则。下面节选了一些要点。

     It seems like an abuse of a mechanism rather than the modeling of a concept from the program's "business domain"

    ……

    My advice is to think very hard before you implement this sort of curious pattern in C#; do the benefits to the customer really outweigh the costs associated with the mental burden you're placing on the code maintainers?

    Eric文中的例子还是很温和的,至少没有导致什么编译错误或是警告。于是就被一些人无视了。

    那么我来写个能出编译错误的例子。 

        public interface SoapArgs<out T>
            where T : SoapArgs<T>
        {
        }
    
        public class GenericSoapArgs<T> : SoapArgs<GenericSoapArgs<T>>
        {
        }
    
        public class DerivedGenericSoapArgs<T> : GenericSoapArgs<T>
        {
        }

    这三个类(或接口)的关系很一目了然对吧。又有这样一个函数,负责把SoapArgs发出去。 

        public class SoapSender
        {
            public virtual void SendSoapArgs<T>(T args)
                where T : SoapArgs<T>
            {
            }
        }

    也很简单对吧?逻辑上,这个函数可以接受前面两个类的实例对吧?可实际上,下面第二行代码会出编译错误。

    new SoapSender().SendSoapArgs(new GenericSoapArgs<int>());
    new SoapSender().SendSoapArgs(new DerivedGenericSoapArgs<int>());

    错误信息是: 

    The type 'DerivedGenericSoapArgs<int>' cannot be used as type parameter 'T' in the generic type or method 'SoapSender.SendSoapArgs<T>(T)'. There is no implicit reference conversion from 'DerivedGenericSoapArgs<int>' to 'SoapArgs< DerivedGenericSoapArgs<int>>'.

    解决办法倒也算简单,让DerivedGenericSoapArgs自己再实现一遍SoapArgs接口就可以了——尽管它的父类已经实现了。

  • 相关阅读:
    从a文件判断是否删除b文件中的行(sed示例)
    绝对路径的表示方式为什么是"/usr"而不是"//usr"
    判断ssh远程命令是否执行结束
    彻底搞懂shell的高级I/O重定向
    Resource Agent:LSB和OCF
    流程控制语句(MySQL/MariaDB )
    MySQL/MariaDB中游标的使用
    翻译:DECLARE HANDLER语句(已提交到MariaDB官方手册)
    从集合的无序性看待关系型数据库中的"序"
    MariaDB/MySQL存储过程和函数
  • 原文地址:https://www.cnblogs.com/nankezhishi/p/selfreferencinggenerics.html
Copyright © 2011-2022 走看看