(原創) 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)
Abstract
在(原創) 一個C++能跑的泛型,但在C#卻不能跑<已解決> (C++) (Template C++) (C#) 中,我們看到了.NET的Generics的multiple constraints是AND的關係,而非OR的關係,若要讓泛型支援OR的關係該如何做呢?
Introduction
我希望有一個Generic Handler,能同時支援Interface1和Interface2,UML表示如下
ISO C++
2(C) OOMusou 2007 http://oomusou.cnblogs.com
3
4Filename : Template_SupportMultiInterface.cpp
5Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6Description : Demo how to use template support multiple interface
7Release : 06/16/2007 1.0
8*/
9#include <iostream>
10
11using namespace std;
12
13class Interface1 {
14public:
15 virtual void func1() = 0;
16 virtual void func2() = 0;
17};
18
19class Interface2 {
20public:
21 virtual void func1() = 0;
22 virtual void func3() = 0;
23};
24
25class Class1 : public Interface1 {
26public:
27 void func1() {
28 cout << "Class1's func1" << endl;
29 }
30 void func2() {
31 cout << "Class1's func2" << endl;
32 }
33};
34
35class Class2 : public Interface2 {
36public:
37 void func1() {
38 cout << "Class2's func1" << endl;
39 }
40 void func3() {
41 cout << "Class2's func3" << endl;
42 }
43};
44
45class IGeneric {
46public:
47 virtual void func1() = 0;
48 virtual void func2() = 0;
49 virtual void func3() = 0;
50};
51
52template<typename T>
53class GenericHandler : public IGeneric {
54private:
55 T* _aClass;
56
57public:
58 GenericHandler(T* aClass) {
59 _aClass = aClass;
60 }
61
62 void func1() {
63 _aClass->func1();
64 }
65
66 void func2() {
67 dynamic_cast<Interface1*>(_aClass)->func2();
68 }
69
70 void func3() {
71 dynamic_cast<Interface2*>(_aClass)->func3();
72 }
73};
74
75
76int main() {
77 Interface1* obj1 = new Class1;
78 Interface2* obj2 = new Class2;
79
80 IGeneric* foo = new GenericHandler<Interface1>(obj1);
81 foo->func1();
82 foo->func2();
83
84 foo = new GenericHandler<Interface2>(obj2);
85 foo->func1();
86 foo->func3();
87}
執行結果
Class1's func2
Class2's func1
Class2's func3
ISO C++的template本來就類似macro,所以code並不讓人訝異,唯一是67行和71行的
是不得已而為之,因為func2本來就是Interface1獨有,而func3也是Interface2所獨有,所以得用casting。
C#
C#的泛型是用Generics,比較類似polymorphism的加強版,最大的特色就是要靠constraints,也因為如此,所以整個架構做了小小的調整,如同上一篇的技巧,將func1往上提到InterfaceBase,讓constraint為InterfaceBase。
2(C) OOMusou 2007 http://oomusou.cnblogs.com
3
4Filename : Generics_SupportMultiInterface.cs
5Compiler : Visual Studio 2005 / C# 2.0
6Description : Demo how to support multiple interface in Generics
7Release : 06/16/2007 1.0
8*/
9using System;
10
11public interface InterfaceBase {
12 void func1();
13}
14
15public interface Interface1 : InterfaceBase {
16 void func2();
17}
18
19public interface Interface2 : InterfaceBase {
20 void func3();
21}
22
23public class Class1 : Interface1 {
24 public void func1() {
25 Console.WriteLine("Class1's func1");
26 }
27
28 public void func2() {
29 Console.WriteLine("Class1's func2");
30 }
31}
32
33public class Class2 : Interface2 {
34 public void func1() {
35 Console.WriteLine("Class2's func1");
36 }
37
38 public void func3() {
39 Console.WriteLine("Class1's func3");
40 }
41}
42
43public interface IGeneric {
44 void func1();
45 void func2();
46 void func3();
47}
48
49public class GenericHandler<T> : IGeneric where T : InterfaceBase {
50 private T _aClass;
51
52 public GenericHandler(T aClass) {
53 _aClass = aClass;
54 }
55
56 public void func1() {
57 _aClass.func1();
58 }
59
60 public void func2() {
61 ((Interface1)_aClass).func2();
62 }
63
64 public void func3() {
65 ((Interface2)_aClass).func3();
66 }
67}
68
69public class main {
70 public static void Main() {
71 Interface1 obj1 = new Class1();
72 Interface2 obj2 = new Class2();
73
74 IGeneric foo = new GenericHandler<Interface1>(obj1);
75 foo.func1();
76 foo.func2();
77
78 foo = new GenericHandler<Interface2>(obj2);
79 foo.func1();
80 foo.func3();
81 }
82}
執行結果
Class1's func2
Class2's func1
Class2's func3
如同ISO C++,61行,65行還是得casting。
C++/CLI
這在C++/CLI就有趣了,因為C++/CLI提供兩種泛型,一種是ISO C++的template,一種是.NET的generics。
使用template
2(C) OOMusou 2006 http://oomusou.cnblogs.com
3
4Filename : Template_SupportMutipleInterface.cpp
5Compiler : Visual C++ 8.0 / C++/CLI
6Description : Demo how to support multiple interface in Generics
7Release : 06/16/2007 1.0
8*/
9#include "stdafx.h"
10
11using namespace System;
12
13public interface class Interface1 {
14 void func1();
15 void func2();
16};
17
18public interface class Interface2 {
19 void func1();
20 void func3();
21};
22
23public ref class Class1 : public Interface1 {
24public:
25 virtual void func1() {
26 Console::WriteLine("Class1's func1");
27 }
28
29 virtual void func2() {
30 Console::WriteLine("Class1's func2");
31 }
32};
33
34public ref class Class2 : public Interface2 {
35public:
36 virtual void func1() {
37 Console::WriteLine("Class2's func1");
38 }
39
40 virtual void func3() {
41 Console::WriteLine("Class2's func3");
42 }
43};
44
45public interface class IGeneric {
46 void func1();
47 void func2();
48 void func3();
49};
50
51template<typename T>
52public ref class GenericHandler : IGeneric {
53private:
54 T^ _aClass;
55
56public:
57 GenericHandler(T^ aClass) {
58 _aClass = aClass;
59 }
60
61 virtual void func1() {
62 _aClass->func1();
63 }
64
65 virtual void func2() {
66 safe_cast<Interface1^>(_aClass)->func2();
67 }
68
69 virtual void func3() {
70 safe_cast<Interface2^>(_aClass)->func3();
71 }
72};
73
74int main() {
75 Interface1^ obj1 = gcnew Class1;
76 Interface2^ obj2 = gcnew Class2;
77
78 IGeneric^ foo = gcnew GenericHandler<Interface1>(obj1);
79 foo->func1();
80 foo->func2();
81
82 foo = gcnew GenericHandler<Interface2>(obj2);
83 foo->func1();
84 foo->func3();
85}
執行結果
Class1's func2
Class2's func1
Class2's func3
使用generics
2(C) OOMusou 2006 http://oomusou.cnblogs.com
3
4Filename : Generic_SupportMutipleInterface.cpp
5Compiler : Visual C++ 8.0 / C++/CLI
6Description : Demo how to support multiple interface in Generics
7Release : 06/16/2007 1.0
8*/
9#include "stdafx.h"
10
11using namespace System;
12
13public interface class InterfaceBase {
14 void func1();
15};
16
17public interface class Interface1 : InterfaceBase {
18 void func2();
19};
20
21public interface class Interface2 : InterfaceBase {
22 void func3();
23};
24
25public ref class Class1 : public Interface1 {
26public:
27 virtual void func1() {
28 Console::WriteLine("Class1's func1");
29 }
30
31 virtual void func2() {
32 Console::WriteLine("Class1's func2");
33 }
34};
35
36public ref class Class2 : public Interface2 {
37public:
38 virtual void func1() {
39 Console::WriteLine("Class2's func1");
40 }
41
42 virtual void func3() {
43 Console::WriteLine("Class2's func3");
44 }
45};
46
47public interface class IGeneric {
48 void func1();
49 void func2();
50 void func3();
51};
52
53generic<typename T>
54 where T : InterfaceBase
55public ref class GenericHandler : IGeneric {
56private:
57 T _aClass;
58
59public:
60 GenericHandler(T aClass) {
61 _aClass = aClass;
62 }
63
64 virtual void func1() {
65 _aClass->func1();
66 }
67
68 virtual void func2() {
69 safe_cast<Interface1^>(_aClass)->func2();
70 }
71
72 virtual void func3() {
73 safe_cast<Interface2^>(_aClass)->func3();
74 }
75};
76
77int main() {
78 Interface1^ obj1 = gcnew Class1;
79 Interface2^ obj2 = gcnew Class2;
80
81 IGeneric^ foo = gcnew GenericHandler<Interface1^>(obj1);
82 foo->func1();
83 foo->func2();
84
85 foo = gcnew GenericHandler<Interface2^>(obj2);
86 foo->func1();
87 foo->func3();
88}
執行結果
Class1's func2
Class2's func1
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++
2(C) OOMusou 2007 http://oomusou.cnblogs.com
3
4Filename : Template_SupportMultiInterface2.cpp
5Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6Description : Demo how to use template support multiple interface
7Release : 06/18/2007 1.0
8*/
9#include <iostream>
10
11using namespace std;
12
13class Interface1 {
14public:
15 virtual void func1() = 0;
16 virtual void func2() = 0;
17};
18
19class Interface2 {
20public:
21 virtual void func1() = 0;
22 virtual void func3() = 0;
23};
24
25class Class1 : public Interface1 {
26public:
27 void func1() {
28 cout << "Class1's func1" << endl;
29 }
30 void func2() {
31 cout << "Class1's func2" << endl;
32 }
33};
34
35class Class2 : public Interface2 {
36public:
37 void func1() {
38 cout << "Class2's func1" << endl;
39 }
40 void func3() {
41 cout << "Class2's func3" << endl;
42 }
43};
44
45class IGenericBase {
46public:
47 virtual void func1() = 0;
48};
49
50class IGeneric1 : public IGenericBase {
51public:
52 virtual void func2() = 0;
53};
54
55class IGeneric2 : public IGenericBase {
56public:
57 virtual void func3() = 0;
58};
59
60template<typename T>
61class GenericHandler : public IGenericBase, IGeneric1, IGeneric2 {
62private:
63 T* _aClass;
64
65public:
66 GenericHandler(T* aClass) {
67 _aClass = aClass;
68 }
69
70 void func1() {
71 _aClass->func1();
72 }
73
74 void func2() {
75 dynamic_cast<Interface1*>(_aClass)->func2();
76 }
77
78 void func3() {
79 dynamic_cast<Interface2*>(_aClass)->func3();
80 }
81};
82
83int main() {
84 Interface1* obj1 = new Class1;
85 Interface2* obj2 = new Class2;
86
87 IGenericBase* foo = new GenericHandler<Interface1>(obj1);
88 foo->func1();
89 dynamic_cast<IGeneric1*>(++foo)->func2();
90
91 foo = new GenericHandler<Interface2>(obj2);
92 foo->func1();
93 dynamic_cast<IGeneric2*>(++++foo)->func3();
94}
執行結果
Class1's func2
Class2's func1
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。
為什麼要++foo和++++foo呢?
因為在87行
foo是一個指向IGenericBase的pointer,若要casting成指向IGeneric1的pointer,其中有offset存在,所以必須++foo,若要指向IGeneric2,其offset是++++foo,詳細原理在Stanley B. Lippman的大作Inside the C++ Object Model有解釋。
C#
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : Generics_SupportMultiInterface2.cs
Compiler : Visual Studio 2005 / C# 2.0
Description : Demo how to support multiple interface in Generics
Release : 06/17/2007 1.0
*/
using System;
public interface InterfaceBase {
void func1();
}
public interface Interface1 : InterfaceBase {
void func2();
}
public interface Interface2 : InterfaceBase {
void func3();
}
public class Class1 : Interface1 {
public void func1() {
Console.WriteLine("Class1's func1");
}
public void func2() {
Console.WriteLine("Class1's func2");
}
}
public class Class2 : Interface2 {
public void func1() {
Console.WriteLine("Class2's func1");
}
public void func3() {
Console.WriteLine("Class2's func3");
}
}
public interface IGenericBase {
void func1();
}
public interface IGeneric1 : IGenericBase {
void func2();
}
public interface IGeneric2 : IGenericBase {
void func3();
}
public class GenericHandler<T> : IGenericBase, IGeneric1, IGeneric2 where T : InterfaceBase {
private T _aClass;
public GenericHandler(T aClass) {
_aClass = aClass;
}
public void func1() {
_aClass.func1();
}
void IGeneric1.func2() {
((Interface1)_aClass).func2();
}
void IGeneric2.func3() {
((Interface2)_aClass).func3();
}
}
public class main {
public static void Main() {
Interface1 obj1 = new Class1();
Interface2 obj2 = new Class2();
IGenericBase foo = new GenericHandler<Interface1>(obj1);
foo.func1();
((IGeneric1)foo).func2();
foo = new GenericHandler<Interface2>(obj2);
foo.func1();
((IGeneric2)foo).func3();
}
}
執行結果
Class1's func2
Class2's func1
Class2's func3
和ISO C++的想法相同,但C#在casting方面就不需考慮offset的問題,且僅使用了C-style的casting。
C++/CLI
使用template
2(C) OOMusou 2006 http://oomusou.cnblogs.com
3
4Filename : Template_SupportMutipleInterface2.cpp
5Compiler : Visual C++ 8.0 / C++/CLI
6Description : Demo how to support multiple interface in Generics
7Release : 06/18/2007 1.0
8*/
9#include "stdafx.h"
10
11using namespace System;
12
13public interface class Interface1 {
14 void func1();
15 void func2();
16};
17
18public interface class Interface2 {
19 void func1();
20 void func3();
21};
22
23public ref class Class1 : public Interface1 {
24public:
25 virtual void func1() {
26 Console::WriteLine("Class1's func1");
27 }
28
29 virtual void func2() {
30 Console::WriteLine("Class1's func2");
31 }
32};
33
34public ref class Class2 : public Interface2 {
35public:
36 virtual void func1() {
37 Console::WriteLine("Class2's func1");
38 }
39
40 virtual void func3() {
41 Console::WriteLine("Class2's func3");
42 }
43};
44
45public interface class IGenericBase {
46 void func1();
47};
48
49public interface class IGeneric1 : public IGenericBase {
50 void func2();
51};
52
53public interface class IGeneric2 : public IGenericBase {
54 void func3();
55};
56
57template<typename T>
58public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 {
59private:
60 T^ _aClass;
61
62public:
63 GenericHandler(T^ aClass) {
64 _aClass = aClass;
65 }
66
67 virtual void func1() {
68 _aClass->func1();
69 }
70
71 virtual void func2() = IGeneric1::func2 {
72 safe_cast<Interface1^>(_aClass)->func2();
73 }
74
75 virtual void func3() = IGeneric2::func3 {
76 safe_cast<Interface2^>(_aClass)->func3();
77 }
78};
79
80int main() {
81 Interface1^ obj1 = gcnew Class1;
82 Interface2^ obj2 = gcnew Class2;
83
84 IGenericBase^ foo = gcnew GenericHandler<Interface1>(obj1);
85 foo->func1();
86 safe_cast<IGeneric1^>(foo)->func2();
87
88 foo = gcnew GenericHandler<Interface2>(obj2);
89 foo->func1();
90 safe_cast<IGeneric2^>(foo)->func3();
91}
執行結果
Class1's func2
Class2's func1
Class2's func3
想法也和ISO C++和C#相同,不過在語法細節上,C++/CLI在casting和explicit interface implementation上和ISO C++與C#不同。
1.casting
72行
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
2(C) OOMusou 2006 http://oomusou.cnblogs.com
3
4Filename : Generic_SupportMutipleInterface2.cpp
5Compiler : Visual C++ 8.0 / C++/CLI
6Description : Demo how to support multiple interface in Generics
7Release : 06/16/2007 1.0
8*/
9#include "stdafx.h"
10
11using namespace System;
12
13public interface class InterfaceBase {
14 void func1();
15};
16
17public interface class Interface1 : InterfaceBase {
18 void func2();
19};
20
21public interface class Interface2 : InterfaceBase {
22 void func3();
23};
24
25public ref class Class1 : public Interface1 {
26public:
27 virtual void func1() {
28 Console::WriteLine("Class1's func1");
29 }
30
31 virtual void func2() {
32 Console::WriteLine("Class1's func2");
33 }
34};
35
36public ref class Class2 : public Interface2 {
37public:
38 virtual void func1() {
39 Console::WriteLine("Class2's func1");
40 }
41
42 virtual void func3() {
43 Console::WriteLine("Class2's func3");
44 }
45};
46
47public interface class IGenericBase {
48 void func1();
49};
50
51public interface class IGeneric1 : public IGenericBase {
52 void func2();
53};
54
55public interface class IGeneric2 : public IGenericBase {
56 void func3();
57};
58
59generic<typename T>
60 where T : InterfaceBase
61public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 {
62private:
63 T _aClass;
64
65public:
66 GenericHandler(T aClass) {
67 _aClass = aClass;
68 }
69
70 virtual void func1() {
71 _aClass->func1();
72 }
73
74 virtual void func2() = IGeneric1::func2 {
75 safe_cast<Interface1^>(_aClass)->func2();
76 }
77
78 virtual void func3() = IGeneric2::func3 {
79 safe_cast<Interface2^>(_aClass)->func3();
80 }
81};
82
83int main() {
84 Interface1^ obj1 = gcnew Class1;
85 Interface2^ obj2 = gcnew Class2;
86
87 IGenericBase^ foo = gcnew GenericHandler<Interface1^>(obj1);
88 foo->func1();
89 safe_cast<IGeneric1^>(foo)->func2();
90
91 foo = gcnew GenericHandler<Interface2^>(obj2);
92 foo->func1();
93 safe_cast<IGeneric2^>(foo)->func3();
94}
C++/CLI若使用generics寫,其實在此範例看不出template和generics的差異,唯一就是在generics需要constraints。
Conclusion
經過幾天的折騰,總算找出了還算滿意的方式,尤其是ISO C++的offset和C++/CLI的explicit interface implementation讓我印象深刻,另外一個遺憾的是,似乎沒用到什麼Design Pattern,只是憑直覺去思考,若有任何建議都非常歡迎。