概述
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 调用方法(注意: 这里使用这些类作为纯函数的类,故使用类似单例的用法。也可以在初始化记录类型,通过反射创建多个实例。)