概述
C++的模板相比於C#的泛型,有很多地方都更加的靈活(雖然代價是降低了編譯速度),比如C++支援變長引數模板、支援列舉、int等型別的值作為模板引數。
C++支援列舉、int等型別的值作為模板引數,為C++的靜態多型程式設計提供了很好的幫助,比如根據列舉值編譯期確定某個物件的行為策略等(下文舉例)。但是C#對這些都是不支援,但是C#天然支援反射,這種需求可以使用反射特性來實現。
需求示例
定義列舉 enum EPlant {Tree, Flower},根據列舉的值列印Tree,Flower字串。注意,這裡的應用場景是編譯器時的多型,即編碼時便確定使用的物件的型別。
C++的實現
上述的例子,C++的語法支援可以天然的實現,如下:
#include <iostream>
enum class EPlant
{
Tree = 0,
Flower,
};
template<EPlant ...Args>
class PrintPlant
{
};
template<>
class PrintPlant<>
{
public:
void Print()
{
std::cout << "Plant" << std::endl;;
}
};
template<>
class PrintPlant<EPlant::Tree>
{
public:
void Print()
{
std::cout << "Tree" << std::endl;;
}
};
template<>
class PrintPlant<EPlant::Flower>
{
public:
void Print()
{
std::cout << "Flower" << std::endl;
}
};
int main()
{
auto plant = new PrintPlant<>();
plant->Print();
auto flower = new PrintPlant<EPlant::Flower>();
flower->Print();
auto tree = new PrintPlant<EPlant::Tree>();
tree->Print();
}
輸出:
- template<EPlant ...Args> 這裡使用變長引數模板,來支援沒有傳入模板引數的情況,特化型別Print函式列印"plant"
- template<> class PrintPlant<EPlant::Tree> 模板特化的型別,在main裡使用了new PrintPlant<EPlant::Tree>();語句建立該型別的物件。該物件列印"Tree"。
C# 實現
C#的模板不支援列舉的值作為模板引數,使用反射進行模擬。
using System;
using System.Reflection;
using System.Collections.Generic;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ABTEX : Attribute
{
public object key;
public ABTEX(object k)
{
key = k;
}
}
public class TEX
{
static Dictionary<Type, Dictionary<Type, Dictionary<string, object>>> dict;
public static void Init(Type[] types)
{
dict = new();
foreach (var t in types)
{
var ABTEX = t.GetCustomAttribute<ABTEX>();
var bt = t.BaseType;
if (ABTEX != null && bt != null)
{
AddInst(t, bt, ABTEX.key);
}
}
}
static string FmtKey(object key)
{
return $"{key}";
}
static void AddInst(Type ty, Type bt, object key)
{
if (!dict.ContainsKey(bt))
{
dict[bt] = new();
}
var kt = key.GetType();
string k = FmtKey(key);
if (!dict[bt].ContainsKey(kt))
{
dict[bt][kt] = new();
}
dict[bt][kt][k] = Activator.CreateInstance(ty);
}
static public R T<R>(object key)
{
if (dict.TryGetValue(typeof(R), out Dictionary<Type, Dictionary<string, object>> dbt))
{
var kt = key.GetType();
string k = FmtKey(key);
if (dbt.TryGetValue(kt, out Dictionary<string, object> kbt))
{
if (kbt.TryGetValue(k, out object ins))
{
return (R)ins;
}
}
}
return default(R);
}
}
public enum EPlant : int
{
None = 0,
Tree,
Flower,
}
public class APrintPlant
{
public virtual void Print()
{
Console.WriteLine("Plant");
}
}
[ABTEX(EPlant.Tree)]
public class PrintTree : APrintPlant
{
public override void Print()
{
Console.WriteLine("Tree");
}
}
[ABTEX(EPlant.Flower)]
public class PrintFlower : APrintPlant
{
public override void Print()
{
Console.WriteLine("Flower");
}
}
class Program
{
static void Main(string[] args)
{
var all = Assembly.GetExecutingAssembly().GetTypes();
TEX.Init(all);
TEX.T<APrintPlant>(EPlant.Tree).Print();
TEX.T<APrintPlant>(EPlant.Flower).Print();
}
}
輸出:
C#可以儲存型別資訊到執行期,透過執行期分析型別資訊建立物件實現靜態多型。
- TEX類分析傳入的所有型別,篩選父類和ABTEX特性,使用父型別,ABTEX的key的型別和值來索引該型別。(這裡索引是例項物件,有需求的話可以儲存型別Type,使用型別透過反射建立物件)
- ABTEX標記需要反射分析的型別,並且標記key。
- Main入口獲取當前程式集下所有的型別資訊,初始化TEX
- 透過TEX.T<抽象類>(key).Func 呼叫方法(注意: 這裡使用這些類作為純函式的類,故使用類似單例的用法。也可以在初始化記錄型別,透過反射建立多個例項。)