Abstract
(原創) 一個C++能跑的泛型,但在C#卻不能跑<已解決> (C++) (Template C++) (C#) 中,我們看到了.NET的Generics的multiple constraints是AND的關係,而非OR的關係,若要讓泛型支援OR的關係該如何做呢?

Introduction
我希望有一個Generic Handler,能同時支援Interface1和Interface2,UML表示如下
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

ISO C++

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


ISO C++的template本來就類似macro,所以code並不讓人訝異,唯一是67行和71行的

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)dynamic_cast<Interface1*>(_aClass)->func2();

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)dynamic_cast<Interface2*>(_aClass)->func3();


是不得已而為之,因為func2本來就是Interface1獨有,而func3也是Interface2所獨有,所以得用casting。

C#
C#的泛型是用Generics,比較類似polymorphism的加強版,最大的特色就是要靠constraints,也因為如此,所以整個架構做了小小的調整,如同上一篇的技巧,將func1往上提到InterfaceBase,讓constraint為InterfaceBase。
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


如同ISO C++,61行,65行還是得casting。

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)((Interface1)_aClass).func2();

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)((Interface2)_aClass).func3();


C++/CLI
這在C++/CLI就有趣了,因為C++/CLI提供兩種泛型,一種是ISO C++的template,一種是.NET的generics。

使用template

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3

使用generics

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


以上做法雖然可行,不過並不滿意,GenericHandler雖然同時支援了func1()、func2()、func3(),但func2()只有在泛型傳入為Interface1時使用才不會出現run-time error,若在傳入Interface2時去invoke了func2(),compiler並不會發現錯誤,要到run-time才發現錯誤。理想上,由於func1()對於Interface1或Interface2都支援,所以無論泛型傳入Interface1或Interface2,Interllisense皆該顯示func1(),但由於func2()只配合Interface1,func3()只配合Interface2,所以foo理想上應該透過一個casting後,才能顯示func2()或func3(),這樣可以避免client誤用而當機。


ISO C++
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


由於分成Interface1和Interface2,所以GenericHandler的Interface也分成Generic1和Generic2。因為func1()為IGeneric1和IGeneric2共用,所以向上提升到IGenericBase,如此設計有兩個好處:
1.GenericHandler有IGenericBase這個最上層的interface,因此可以配合眾多creational pattern合作。
2.要使用func2()時必須明確轉型成IGeneric1,要使用func3()時必須明確轉型成IGeneric2,如此可避免client誤用而導致run-time error。

若用ISO C++實做,89行和93行非常tricky。

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)dynamic_cast<IGeneric1*>(++foo)->func2();

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)dynamic_cast<IGeneric2*>(++++foo)->func3();


為什麼要++foo和++++foo呢?
因為在87行

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)IGenericBase* foo = new GenericHandler<Interface1>(obj1);


foo是一個指向IGenericBase的pointer,若要casting成指向IGeneric1的pointer,其中有offset存在,所以必須++foo,若要指向IGeneric2,其offset是++++foo,詳細原理在Stanley B. Lippman的大作Inside the C++ Object Model有解釋。

C#
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


和ISO C++的想法相同,但C#在casting方面就不需考慮offset的問題,且僅使用了C-style的casting。

C++/CLI
使用template

 1}


執行結果

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class1's func2
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func1
(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)Class2's func3


想法也和ISO C++和C#相同,不過在語法細節上,C++/CLI在casting和explicit interface implementation上和ISO C++與C#不同。

1.casting
72行

(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)safe_cast<Interface1^>(_aClass)->func2();


ISO C++在casting上有const_cast,dynamic_cast,reinterpret_cast和static_cast,在C++/CLI仍然可用,除此之外,C++/CLI另外提出了safe_cast,專門應付managed部分。

2.explicit interface implementation
71行

{


就是C#的

{


ISO C++並沒有這樣的語法,這是.NET CLI規格新加上去的。

使用generics

 1}


C++/CLI若使用generics寫,其實在此範例看不出template和generics的差異,唯一就是在generics需要constraints。

Conclusion
經過幾天的折騰,總算找出了還算滿意的方式,尤其是ISO C++的offset和C++/CLI的explicit interface implementation讓我印象深刻,另外一個遺憾的是,似乎沒用到什麼Design Pattern,只是憑直覺去思考,若有任何建議都非常歡迎。

相关文章: