4

C#模拟C++模板特化对类型的值的支持 - 寡人正在Coding

 1 year ago
source link: https://www.cnblogs.com/hggzhang/p/17325713.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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();
}

输出:
image

  • template<EPlant ...Args> 这里使用变长参数模板,来支持没有传入模板参数的情况,特化类型Print函数打印"plant"
  • template<> class PrintPlant<EPlant::Tree> 模板特化的类型,在main里使用了new PrintPlant<EPlant::Tree>();语句创建该类型的对象。该对象打印"Tree"。

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();
    }
}

输出:
image
C#可以保存类型信息到运行期,通过运行期分析类型信息创建对象实现静态多态。

  • TEX类分析传入的所有类型,筛选父类和ABTEX特性,使用父类型,ABTEX的key的类型和值来索引该类型。(这里索引是实例对象,有需求的话可以保存类型Type,使用类型通过反射创建对象)
  • ABTEX标记需要反射分析的类型,并且标记key。
  • Main入口获取当前程序集下所有的类型信息,初始化TEX
  • 通过TEX.T<抽象类>(key).Func 调用方法(注意: 这里使用这些类作为纯函数的类,故使用类似单例的用法。也可以在初始化记录类型,通过反射创建多个实例。)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK