zoukankan      html  css  js  c++  java
  • 也谈用反射实现Enum→String映射:一种重视性能的方法【转载】

    一、问题的提出

    最近,博客园有许多blogger提出了为枚举显示中文名称的文章,例如[让枚举成员显示出中文信息],[利用自定义属性,定义枚举值的详细文本],[细节决定成败:映射枚举],[利用DescriptionAttribute定义枚举值的描述信息],还有原来看过的一些文章(不好意思地址没记)。这些文章的共同特点就是,使用了自定义Attribute附加在枚举值上, 在运行时获取枚举相关的信息。

    这 种方法中,由于是使用反射,因为有些人关心其中的性能问题——特别是处理大量数据的时候,例如将大量枚举导入到DataGrid的时候;而且人们也发 现,Enum本身的ToString方法也使用了反射的方法,因此实际上也存在着速度慢的问题。本文试着以性能为重点,在不失去结构的易读性、可扩展性的 条件下,基于以上各位高手的经验,给出一种重视性能的方法。

    设计目标:

    1,枚举定义形式上使用容易读写的附加Attribute的形式;
    2,支持多语言版本,可以很容易地被本地化;
    3,调用格式简单。

    二、ToString()的性能问题
    对于一个枚举值emItem,用下列代码进行测试:

                for (int i = 0; i < 1000000; i++)
                {
                    s 
    = emItem.ToString();
                }


    其中s是String类型,emItem是一个MyEnum类型的枚举。
    在我的机器上,该循环要花费4900毫秒左右。

    当我把其中的“s = emItem.ToString();”换成“s = Enum.GetName(typeof(MyEnum), emItem);”之后,这个时间减少到2300毫秒。

    但是必须注意的是,ToString方法和GetName的方法并不是相同的;但是有些时候对于我们来说也许用哪个都可以。
    因此我的第一个建议就是,如果可以互换的话,使用GetName代替ToString。

    三、 反射的性能问题
    显然,上面的两个方法ToString和GetName,都不能解决显示枚举的自定义名,以及提供不同语言版本的问题。因此,很多人采用了反射的方法,像下面这样为每个枚举值增加了Attribute:

        public enum MyEnum
        {
            [EnumItemDescription(
    "Description1")]
            EnumValue1 
    = 1,
            [EnumItemDescription(
    "Description2")]
            EnumValue2 
    = 2,
            [EnumItemDescription(
    "Description3")]
            EnumValue3 
    = 4,
        }


    其中,EnumItemDescriptionAttribute是类似于DescriptionAttribute的类。
    这样做起来的确非常优雅;在读取该Attribute的值时,大多数使用的是如下的格式:

            static string GetStringFromEnum(Enum enumvalue)
            {
                FieldInfo finfo 
    = enumvalue.GetType().GetField(enumvalue.ToString());
                
    object[] cAttr = finfo.GetCustomAttributes(typeof(EnumItemDescriptionAttribute), true);
                
    if (cAttr.Length > 0)
                {
                    EnumItemDescriptionAttribute desc 
    = cAttr[0as EnumItemDescriptionAttribute;
                    
    if (desc != null)
                    {
                        
    return desc.Description;
                    }
                }
                
    return enumvalue.ToString();
            }


    事实上,这已经是简化的模式——它没有进行循环。实际上看到的许多blogger的程序中,对所有的FieldInfo进行循环,逐一比较其名字,然后还要对每个FieldInfo的每个Attribute进行循环——也就是说,复杂度是O(n^2)。

    那么,当我们用“s = GetStringFromEnum(emItem);”来进行我们进行的第一个实验时,结果是多少呢?

    结果是,当我等到30秒的时候我终于不耐烦了;当我正想强行关闭它的时候,它结束了——32秒,即3万2千毫秒。

    想想看,它慢也是当然的——每次将一个枚举值映射为字符串时,都要进行反射调用,而且每次还都要调用Enum.ToString这个本来就慢腾腾的家伙!


    四、Dictionay + Reflection的缓存式实现尝试
    我们回头来想一想,我们为什么必须,或者说更喜欢在这里使用反射?
    因为如果不用反射,我们就必须写一个像下面这样的映射函数:

            static string StringFromEnum(MyEnum enumValue)
            {
                
    switch (enumValue)
                {
                    
    case MyEnum.EnumValue1:
                        
    return "String1";
                    
    case MyEnum.EnumValue2:
                        
    return "String2";
                    
    case MyEnum.EnumValue3:
                        
    return "String3";
                }
                
    return enumValue.ToString();
            }


    (或者我们也可以用一个Dictionary<MyEnum, string>来维护)

    也就是说,这样就把“枚举值”和“枚举值的名字”割裂开来了;从设计的角度来说,这样的确为以后的维护增加了困难;但是这样做的速度的确很快。

    那么,我们如果把这二者结合起来,不就完美了吗?首先用反射读取所有的Attribute,然后将之存储到一个列表备用;以后每次调用时,不再进行反射调用,而是查询这个列表(相当于缓存)不就可以了吗?程序如下:

        public class EnumMap
        {
            
    private Type internalEnumType;
            
    private Dictionary<Enum, string> map;

            
    public EnumMap(Type enumType)
            {
                
    if (!enumType.IsSubclassOf(typeof(Enum)))
                {
                    
    throw new InvalidCastException();
                }
                internalEnumType 
    = enumType;
                FieldInfo[] staticFiles 
    = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);

                map 
    = new Dictionary<Enum, string>(staticFiles.Length);

                
    for (int i = 0; i < staticFiles.Length; i++)
                {
                    
    if (staticFiles[i].FieldType == enumType)
                    {
                        
    string description = "";
                        
    object[] attrs = staticFiles[i].GetCustomAttributes(typeof(EnumItemDescriptionAttribute), true);
                        description 
    = attrs.Length > 0 ?
                            ((EnumItemDescriptionAttribute)attrs[
    0]).Description :
                            
    //若没找到EnumItemDescription标记,则使用该枚举值的名字
                            description = staticFiles[i].Name;

                        map.Add((Enum)staticFiles[i].GetValue(enumType), description);
                    }
                }
            }

            
    public string this[Enum item]
            {
                
    get
                {
                    
    if (item.GetType() != internalEnumType)
                    {
                        
    throw new ArgumentException();
                    }
                    
    return map[item];
                }
            }
        }


    这样,我们只需要首先创建一个该类型的实例:
                EnumMap myEnumMap = new EnumMap(typeof(MyEnum));
    然后,在任何需要映射枚举值为字符串的地方,像这样调用:
                s = myEnumMap[emItem];
    就可以了。

    那么,使用“s = myEnumMap[emItem];”进行最开始的哪个测试,结果如何呢?
    结果是650毫秒——是不用“缓存”时耗费时间的50分之一。

    这里我们注意到,直接提供EnumMap类可能会造成若干问题,而且对于每种枚举类型,我们都要为之新建一个EnumMap对象,比较麻烦;因此我们对其进行如下简单封装,一方面保证其Singleton特性,一方面不用再去一个个创建EnumMap对象了。


        public class EnumMapHelper
        {
            
    // maps用于保存每种枚举及其对应的EnumMap对象
            private static Dictionary<Type, EnumMap> maps;

            
    // 由于C#中没有static indexer的概念,所以在这里我们用静态方法
            public static string GetStringFromEnum(Enum item)
            {
                
    if (maps == null)
                {
                    maps 
    = new Dictionary<Type, EnumMap>();
                }

                Type enumType 
    = item.GetType();

                EnumMap mapper 
    = null;
                
    if (maps.ContainsKey(enumType))
                {
                    mapper 
    = maps[enumType];
                }
                
    else
                {
                    mapper 
    = new EnumMap(enumType);
                    maps.Add(enumType, mapper);
                }
                
    return mapper[item];
            }

            
    private class EnumMap
            {
                
    private Type internalEnumType;
                
    private Dictionary<Enum, string> map;

                
    public EnumMap(Type enumType)
                {
                    
    if (!enumType.IsSubclassOf(typeof(Enum)))
                    {
                        
    throw new InvalidCastException();
                    }
                    internalEnumType 
    = enumType;
                    FieldInfo[] staticFiles 
    = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);

                    map 
    = new Dictionary<Enum, string>(staticFiles.Length);

                    
    for (int i = 0; i < staticFiles.Length; i++)
                    {
                        
    if (staticFiles[i].FieldType == enumType)
                        {
                            
    string description = "";
                            
    object[] attrs = staticFiles[i].GetCustomAttributes(typeof(EnumItemDescriptionAttribute), true);
                            description 
    = attrs.Length > 0 ?
                                ((EnumItemDescriptionAttribute)attrs[
    0]).Description :
                                
    //若没找到EnumItemDescription标记,则使用该枚举值的名字
                                description = staticFiles[i].Name;

                            map.Add((Enum)staticFiles[i].GetValue(enumType), description);
                        }
                    }
                }

                
    public string this[Enum item]
                {
                    
    get
                    {
                        
    if (item.GetType() != internalEnumType)
                        {
                            
    throw new ArgumentException();
                        }
                        
    return map[item];
                    }
                }
            }
        }


    调用时,形式简单,只需要一条语句即可:


    s = EnumMapHelper.GetStringFromEnum(emItem);


    五、对多语言的支持
    对多语言的支持方面,我们只需要照着FCL的样子画瓢就可以了:

        public class EnumItemDescriptionAttribute : DescriptionAttribute
        {
            
    private bool replaced;
     
            
    public EnumItemDescriptionAttribute(string description)
                : 
    base(description)
            {
            }
            
    public override string Description
            {
                
    get
                {
                    
    if (!this.replaced)
                    {
                        
    this.replaced = true;
                        
    base.DescriptionValue = SR.GetString(base.Description);
                    }
                    
    return base.Description;
                }
            }
        }


    其中的SR是同步读取资源的类,与内容关系不大,这里就略去了(可以参考FCL的SR的实现)。

    到此为止,一个快速(比Enum本身的ToString方法还要快4倍),形式简洁(无论是声明形式还是调用形式),支持多语言的映射类就完成了。
    其中可能有若干bug,并且几乎没有考虑异常,欢迎大家提意见和建议。

  • 相关阅读:
    vue element 表格错位问题
    echarts tooltip 按值的降序显示 tip 信息
    前端 玫瑰花小样式
    echarts X轴数据过多批量显示
    微信js sdk的使用初步理解
    对象 的循环嵌套
    移动端拉起电话请求
    js后加版本号
    数组排序于数组去重
    es6数组的方法
  • 原文地址:https://www.cnblogs.com/xyqCreator/p/2436271.html
Copyright © 2011-2022 走看看