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

 1/* 
 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 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。

 1/* 
 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 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

 1/* 
 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 func1
Class1's func2
Class2's func1
Class2's func3

使用generics

 1/* 
 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 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++

 1/* 
 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 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

 1/* 
 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 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

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

posted on 2007-06-16 20:37  真 OO无双  阅读(4790)  评论(26编辑  收藏  举报

导航