惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
Security Archives - TechRepublic
Security Archives - TechRepublic
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
CERT Recently Published Vulnerability Notes
S
Security @ Cisco Blogs
S
Security Affairs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
Lohrmann on Cybersecurity
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
A
Arctic Wolf
NISL@THU
NISL@THU
P
Proofpoint News Feed
W
WeLiveSecurity
S
Schneier on Security
AI
AI
Schneier on Security
Schneier on Security
N
News and Events Feed by Topic
L
LINUX DO - 最新话题
Cisco Talos Blog
Cisco Talos Blog
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
V
Vulnerabilities – Threatpost
Cyberwarzone
Cyberwarzone
I
Intezer
S
Securelist
Help Net Security
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
小众软件
小众软件
Last Week in AI
Last Week in AI
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
WordPress大学
WordPress大学
罗磊的独立博客
月光博客
月光博客
雷峰网
雷峰网
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 司徒正美
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events

博客园 - 南柯之石

用户中心 - 博客园 一定间隔时间下重复执行一个函数的几个方法 为什么.NET Framework就没有个专门的P/Invoke Library? Which would you perfer? "easy and fast and works well" or "hard and time consuming and error prone" 在ListView的GroupItem头中显示每列的Summary 小例子背后的大道理——用户需求+设计原则+正确应用 =设计方案 GET和POST有什么区别?及为什么网上的多数答案都是错的。 小例子背后的大道理——Adapter模式详解 小例子背后的大道理——从DIP中“倒置”的含义说接口的正确使用 XmlSerializer, DataContractSerializer 和 BinaryFormatter区别与用法分析 使用WPF开发的扫雷游戏,双系统主题复刻版 技术误用带来的技术偏见及所谓的“经验” 从一个UI交互设计师的讲座说开去 NoSQL和MemeryCache的出现意味着传统数据库使用方式的变革吗? 不使用反射进行C#属性的运行时动态访问 一次模块划分的争论及其结局 电子书籍质量保证事件分析 在SQL Server中调用.NET程序集 T-SQL 操作XML示例
自引用泛型模式分析
南柯之石 · 2014-04-10 · via 博客园 - 南柯之石

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

    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接口就可以了——尽管它的父类已经实现了。