曾經(jīng)有人問我這樣一個問題:如何迫使子類提供無參構(gòu)造函數(shù)。當(dāng)時給出的答案是讓子類實(shí)現(xiàn)這樣一個接口。
public interface IMustHaveParameterLessConstructor<T> where T : IMustHaveParameterLessConstructor<T>, new() { }這種在泛型參數(shù)中引用自身的技法,還有個名字,叫做“Self-Referencing Generics”模式。這個技法在C++中已經(jīng)被使用了20多年,只不過叫做Curiously Recurring Template。
這個技法可以用來實(shí)現(xiàn)不少有用的功能。比如為所有子類實(shí)現(xiàn)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> { }下面談?wù)勥@個技法的劣勢。
首先,它影響代碼的可讀性,比如說用到這種程度的時候。
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> { }這就是自找麻煩了。別人讀起來也會想罵人。
其次,這個技法其實(shí)是反面向?qū)ο蟮?。如果你的類繼承層次多于一層,就會產(chǎn)生問題。
Eric Lippert在其博文《Curiouser and curiouser》中從繼承關(guān)系的邏輯合理性的角度進(jìn)行了分析。本質(zhì)上講,自引用泛型違反了里氏替換原則。下面節(jié)選了一些要點(diǎn)。
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文中的例子還是很溫和的,至少沒有導(dǎo)致什么編譯錯誤或是警告。于是就被一些人無視了。
那么我來寫個能出編譯錯誤的例子。
public interface SoapArgs<out T> where T : SoapArgs<T> { } public class GenericSoapArgs<T> : SoapArgs<GenericSoapArgs<T>> { } public class DerivedGenericSoapArgs<T> : GenericSoapArgs<T> { }這三個類(或接口)的關(guān)系很一目了然對吧。又有這樣一個函數(shù),負(fù)責(zé)把SoapArgs發(fā)出去。
public class SoapSender { public virtual void SendSoapArgs<T>(T args) where T : SoapArgs<T> { } }也很簡單對吧?邏輯上,這個函數(shù)可以接受前面兩個類的實(shí)例對吧?可實(shí)際上,下面第二行代碼會出編譯錯誤。
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自己再實(shí)現(xiàn)一遍SoapArgs接口就可以了——盡管它的父類已經(jīng)實(shí)現(xiàn)了。
新聞熱點(diǎn)
疑難解答
圖片精選