(原創) 如何讓泛型支援多個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.com3

4
Filename : Template_SupportMultiInterface.cpp5
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++6
Description : Demo how to use template support multiple interface7
Release : 06/16/2007 1.08
*/9
#include <iostream>10

11
using namespace std;12

13
class Interface1 {14
public:15
virtual void func1() = 0;16
virtual void func2() = 0;17
};18

19
class Interface2 {20
public:21
virtual void func1() = 0;22
virtual void func3() = 0;23
};24

25
class Class1 : public Interface1 {26
public:27
void func1() {28
cout << "Class1's func1" << endl;29
}30
void func2() {31
cout << "Class1's func2" << endl;32
}33
};34

35
class Class2 : public Interface2 {36
public:37
void func1() {38
cout << "Class2's func1" << endl;39
}40
void func3() {41
cout << "Class2's func3" << endl;42
}43
};44

45
class IGeneric {46
public:47
virtual void func1() = 0;48
virtual void func2() = 0;49
virtual void func3() = 0;50
};51

52
template<typename T>53
class GenericHandler : public IGeneric {54
private:55
T* _aClass;56

57
public: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

76
int 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 func1
Class1's func2
Class2's func1
Class2's func3
ISO C++的template本來就類似macro,所以code並不讓人訝異,唯一是67行和71行的
dynamic_cast<Interface1*>(_aClass)->func2();
dynamic_cast<Interface2*>(_aClass)->func3();
是不得已而為之,因為func2本來就是Interface1獨有,而func3也是Interface2所獨有,所以得用casting。
C#
C#的泛型是用Generics,比較類似polymorphism的加強版,最大的特色就是要靠constraints,也因為如此,所以整個架構做了小小的調整,如同上一篇的技巧,將func1往上提到InterfaceBase,讓constraint為InterfaceBase。
/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : Generics_SupportMultiInterface.cs5
Compiler : Visual Studio 2005 / C# 2.06
Description : Demo how to support multiple interface in Generics7
Release : 06/16/2007 1.08
*/9
using System;10

11
public interface InterfaceBase {12
void func1();13
}14

15
public interface Interface1 : InterfaceBase {16
void func2();17
}18

19
public interface Interface2 : InterfaceBase {20
void func3();21
}22

23
public 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

33
public 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

43
public interface IGeneric {44
void func1();45
void func2();46
void func3();47
}48

49
public 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

69
public 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 func1
Class1's func2
Class2's func1
Class2's func3
如同ISO C++,61行,65行還是得casting。
((Interface1)_aClass).func2();
((Interface2)_aClass).func3();
C++/CLI
這在C++/CLI就有趣了,因為C++/CLI提供兩種泛型,一種是ISO C++的template,一種是.NET的generics。
使用template
/* 2
(C) OOMusou 2006 http://oomusou.cnblogs.com3

4
Filename : Template_SupportMutipleInterface.cpp5
Compiler : Visual C++ 8.0 / C++/CLI6
Description : Demo how to support multiple interface in Generics7
Release : 06/16/2007 1.08
*/9
#include "stdafx.h"10

11
using namespace System;12

13
public interface class Interface1 {14
void func1();15
void func2();16
};17

18
public interface class Interface2 {19
void func1();20
void func3();21
};22

23
public ref class Class1 : public Interface1 {24
public: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

34
public ref class Class2 : public Interface2 {35
public: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

45
public interface class IGeneric {46
void func1();47
void func2();48
void func3();49
};50

51
template<typename T>52
public ref class GenericHandler : IGeneric {53
private:54
T^ _aClass;55

56
public: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

74
int 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 func1
Class1's func2
Class2's func1
Class2's func3使用generics
/* 2
(C) OOMusou 2006 http://oomusou.cnblogs.com3

4
Filename : Generic_SupportMutipleInterface.cpp5
Compiler : Visual C++ 8.0 / C++/CLI6
Description : Demo how to support multiple interface in Generics7
Release : 06/16/2007 1.08
*/9
#include "stdafx.h"10

11
using namespace System;12

13
public interface class InterfaceBase {14
void func1();15
};16

17
public interface class Interface1 : InterfaceBase {18
void func2();19
};20

21
public interface class Interface2 : InterfaceBase {22
void func3();23
};24

25
public ref class Class1 : public Interface1 {26
public: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

36
public ref class Class2 : public Interface2 {37
public: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

47
public interface class IGeneric {48
void func1();49
void func2();50
void func3();51
};52

53
generic<typename T> 54
where T : InterfaceBase55
public ref class GenericHandler : IGeneric {56
private:57
T _aClass;58

59
public: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

77
int 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 func1
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.com3

4
Filename : Template_SupportMultiInterface2.cpp5
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++6
Description : Demo how to use template support multiple interface7
Release : 06/18/2007 1.08
*/9
#include <iostream>10

11
using namespace std;12

13
class Interface1 {14
public:15
virtual void func1() = 0;16
virtual void func2() = 0;17
};18

19
class Interface2 {20
public:21
virtual void func1() = 0;22
virtual void func3() = 0;23
};24

25
class Class1 : public Interface1 {26
public:27
void func1() {28
cout << "Class1's func1" << endl;29
}30
void func2() {31
cout << "Class1's func2" << endl;32
}33
};34

35
class Class2 : public Interface2 {36
public:37
void func1() {38
cout << "Class2's func1" << endl;39
}40
void func3() {41
cout << "Class2's func3" << endl;42
}43
};44

45
class IGenericBase {46
public:47
virtual void func1() = 0;48
};49

50
class IGeneric1 : public IGenericBase {51
public:52
virtual void func2() = 0;53
};54

55
class IGeneric2 : public IGenericBase {56
public:57
virtual void func3() = 0;58
};59

60
template<typename T>61
class GenericHandler : public IGenericBase, IGeneric1, IGeneric2 {62
private:63
T* _aClass;64

65
public: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

83
int 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 func1
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。
dynamic_cast<IGeneric1*>(++foo)->func2();
dynamic_cast<IGeneric2*>(++++foo)->func3();
為什麼要++foo和++++foo呢?
因為在87行
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#
/*
(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 func1
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.com3

4
Filename : Template_SupportMutipleInterface2.cpp5
Compiler : Visual C++ 8.0 / C++/CLI6
Description : Demo how to support multiple interface in Generics7
Release : 06/18/2007 1.08
*/9
#include "stdafx.h"10

11
using namespace System;12

13
public interface class Interface1 {14
void func1();15
void func2();16
};17

18
public interface class Interface2 {19
void func1();20
void func3();21
};22

23
public ref class Class1 : public Interface1 {24
public: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

34
public ref class Class2 : public Interface2 {35
public: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

45
public interface class IGenericBase {46
void func1();47
};48

49
public interface class IGeneric1 : public IGenericBase {50
void func2();51
};52

53
public interface class IGeneric2 : public IGenericBase {54
void func3();55
};56

57
template<typename T>58
public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 {59
private:60
T^ _aClass;61

62
public: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

80
int 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 func1
Class1's func2
Class2's func1
Class2's func3
想法也和ISO C++和C#相同,不過在語法細節上,C++/CLI在casting和explicit interface implementation上和ISO C++與C#不同。
1.casting
72行
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行
virtual void func2() = IGeneric1::func2 {
就是C#的
void IGeneric1.func2() {
ISO C++並沒有這樣的語法,這是.NET CLI規格新加上去的。
使用generics
/* 2
(C) OOMusou 2006 http://oomusou.cnblogs.com3

4
Filename : Generic_SupportMutipleInterface2.cpp5
Compiler : Visual C++ 8.0 / C++/CLI6
Description : Demo how to support multiple interface in Generics7
Release : 06/16/2007 1.08
*/9
#include "stdafx.h"10

11
using namespace System;12

13
public interface class InterfaceBase {14
void func1();15
};16

17
public interface class Interface1 : InterfaceBase {18
void func2();19
};20

21
public interface class Interface2 : InterfaceBase {22
void func3();23
};24

25
public ref class Class1 : public Interface1 {26
public: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

36
public ref class Class2 : public Interface2 {37
public: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

47
public interface class IGenericBase {48
void func1();49
};50

51
public interface class IGeneric1 : public IGenericBase {52
void func2();53
};54

55
public interface class IGeneric2 : public IGenericBase {56
void func3();57
};58

59
generic<typename T> 60
where T : InterfaceBase61
public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 {62
private:63
T _aClass;64
65
public: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

83
int 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,只是憑直覺去思考,若有任何建議都非常歡迎。


浙公网安备 33010602011771号