• C#模拟C++模板特化对类型的值的支持


    概述

    C++的模板相比于C#的泛型,有很多地方都更加的灵活(虽然代价是降低了编译速度),比如C++支持变长参数模板、支持枚举、int等类型的值作为模板参数。
    C++支持枚举、int等类型的值作为模板参数,为C++的静态多态编程提供了很好的帮助,比如根据枚举值编译期确定某个对象的行为策略等(下文举例)。但是C#对这些都是不支持,但是C#天然支持反射,这种需求可以使用反射特性来实现。

    需求示例

    定义枚举 enum EPlant {Tree, Flower},根据枚举的值打印Tree,Flower字符串。注意,这里的应用场景是编译器时的多态,即编码时便确定使用的对象的类型。

    C++的实现

    上述的例子,C++的语法支持可以天然的实现,如下:

    #include 
    
    enum class EPlant
    {
        Tree = 0,
        Flower,
    };
    
    template
    class PrintPlant
    {
        
    };
    
    template<>
    class PrintPlant<>
    {
    public:
        void Print()
        {
            std::cout << "Plant" << std::endl;;
        }
    };
    
    template<>
    class PrintPlant
    {
    public: 
        void Print()
        {
            std::cout << "Tree" << std::endl;;
        }
    };
    
    template<>
    class PrintPlant
    {
    public:
        void Print()
        {
            std::cout << "Flower" << std::endl;
        }
    };
    
    int main()
    {
        auto plant = new PrintPlant<>();
        plant->Print();
        auto flower = new PrintPlant();
        flower->Print();
        auto tree = new PrintPlant();
        tree->Print();
    }
    

    输出:
    image

    • template 这里使用变长参数模板,来支持没有传入模板参数的情况,特化类型Print函数打印"plant"
    • template<> class PrintPlant 模板特化的类型,在main里使用了new PrintPlant();语句创建该类型的对象。该对象打印"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 Dictionarystring, object>>> dict;
        public static void Init(Type[] types)
        {
            dict = new();
            foreach (var t in types)
            {
                var ABTEX = t.GetCustomAttribute();
                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 Dictionarystring, 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(EPlant.Tree).Print();
            TEX.T(EPlant.Flower).Print();
        }
    }
    
    

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

    • TEX类分析传入的所有类型,筛选父类和ABTEX特性,使用父类型,ABTEX的key的类型和值来索引该类型。(这里索引是实例对象,有需求的话可以保存类型Type,使用类型通过反射创建对象)
    • ABTEX标记需要反射分析的类型,并且标记key。
    • Main入口获取当前程序集下所有的类型信息,初始化TEX
    • 通过TEX.T<抽象类>(key).Func 调用方法(注意: 这里使用这些类作为纯函数的类,故使用类似单例的用法。也可以在初始化记录类型,通过反射创建多个实例。)
  • 相关阅读:
    【深入理解Kotlin协程】协程的分类、与线程的区别
    .NET Emit 入门教程:第六部分:IL 指令:8:详解 ILGenerator 指令方法:类型转换指令
    解决onebot提示当前QQ版本过低,请升级到最新版在登录!
    编码中的Adapter,不仅是一种设计模式,更是一种架构理念与解决方案
    前端学习第三天-css基础
    【C++笔记】模板进阶
    查看Visual Studio软件_MSC_VER值(MSVC编译器版本)的方法
    Array.reduce() 详解
    免编程经验,搭建宠物店小程序轻松实现
    【C语言|关键字】C语言32个关键字详解
  • 原文地址:https://www.cnblogs.com/hggzhang/p/17325713.html