如何讓泛型支援多個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表示如下
 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

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
11 using   namespace  std;
12
13 class  Interface1  {
14public:
15  virtual void func1() = 0;
16  virtual void func2() = 0;
17}
;
18
19 class  Interface2  {
20public:
21  virtual void func1() = 0;
22  virtual void func3() = 0;
23}
;
24
25 class  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
35 class  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
45 class  IGeneric  {
46public:
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  {
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
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。
 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

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

 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

 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
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  {
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
34 public   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
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  {
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
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

 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
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  {
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
36 public   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
47 public   interface   class  IGeneric  {
48  void func1();
49  void func2();
50  void func3();
51}
;
52
53 generic < typename T >  
54   where T : InterfaceBase
55 public   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
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++
 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

 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
11 using   namespace  std;
12
13 class  Interface1  {
14public:
15  virtual void func1() = 0;
16  virtual void func2() = 0;
17}
;
18
19 class  Interface2  {
20public:
21  virtual void func1() = 0;
22  virtual void func3() = 0;
23}
;
24
25 class  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
35 class  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
45 class  IGenericBase  {
46public:
47  virtual void func1() = 0;
48}
;
49
50 class  IGeneric1 :  public  IGenericBase  {
51public:
52  virtual void func2() = 0;
53}
;
54
55 class  IGeneric2 :  public  IGenericBase  {
56public:
57  virtual void func3() = 0;
58}
;
59
60 template < typename T >
61 class  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
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#
 如何讓泛型支援多個interface? (.NET) (C/C++) (C#) (template) (C++/CLI)

/* 
(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
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  {
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
34 public   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
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  {
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
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

 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
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  {
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
36 public   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
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 : InterfaceBase
61 public   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
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,只是憑直覺去思考,若有任何建議都非常歡迎。

你可能感兴趣的:(interface)