常用扩展之枚举扩展

项目在开发的过程中,经常会遇到一些前台显示和后台代码不相同的属性,比如布尔类型和枚举类型的属性要转换为相应的中文说明。而且,转换也会有千变万化的可能,转换为一个下拉列表框和网格中的一个列就不一样。

这个时候,可能会有几种解决方案:最简单的就是使用switch语句,写成一个扩展方法,然后在前台绑定的时候使用扩展方法解决。这种方法简单,但是没遇到一种就得写一个扩展方法,不具有通用性。

我这里要说的是基于Attribute的一种比较通用的方案。我们都知道,.Net能够在类的成员上添加特性(Attribute),增加一些我们自己定制的东西。所以,我就在枚举的这些成员上加上了一个自定义的特性,保存我要转换的中文注释。我自定义的特性代码如下:

/// <summary>
/// 字段或属性的中文解释属性
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class Desc : Attribute
{
    /// <summary>
    /// 获得字段或属性的中文解释.
    /// </summary>
    /// <value>字段或属性的中文解释.</value>
    public string Value { get; private set; }
    /// <summary>
    /// 初始化创建一个 <see cref="Desc"/> 类的实例, 用于指示字段或属性的解释说明.
    /// </summary>
    /// <param name="value">字段或属性的解释说明.</param>
    public Desc(string value)
    {
        Value = value;
    }
}

之后,通过反射获取这个属性,进而获取我编程的时候为其添加的中文注释。做成扩展方法之后,就可以对所有的枚举使用了。不过因为使用了反射,在密集调用的时候效率上会损失一点,不知道各位有没有更好的解决方案。

/// <summary>
/// 获得枚举字段的特性(Attribute),该属性不允许多次定义。
/// </summary>
/// <typeparam name="T">特性类型。</typeparam>
/// <param name="value">一个枚举的实例对象。</param>
/// <returns>枚举字段的扩展属性。如果不存在则返回 <c>null</c> 。</returns>
public static T GetAttribute<T>(this Enum value) where T : Attribute
{
    FieldInfo field = value.GetType().GetField(value.ToString());
    return Attribute.GetCustomAttribute(field, typeof(T)) as T;
}

我先说明一下,C#里面所有的枚举(enum)都是从Enum这个类继承的。所以将Enum填在这里,就能在所有的枚举实例上使用这个扩展方法。

约束T类型必须从Attribute这个类继承,是因为C#中规定能够追加在字段上的特性必须从Attribute类继承。对于这一点不理解的话,通过Google搜索一下,应该很快就可以得到答案。

经过了这两个准备之后,只要我们在枚举的成员上加上了特性,就能够通过这个扩展方法访问了,而且不局限于我提供的这个属性。我这里举个例子简单说明一下:

/// <summary>
/// 客户状态
/// </summary>
[Serializable]
public enum CustomerStateType
{
    /// <summary>
    /// [默认值] 活动状态。
    /// </summary>
    [Desc("活动")]
    Active = 0,
 
    /// <summary>
    /// 终止状态。
    /// </summary>
    [Desc("终止")]
    End = 1,
}

定义完了之后,我们就可以通过扩展方法获得这个所谓的“活动”和“终止”了。代码如下:

// 假设这里是经过了一系列计算得到的结果。
CustomerStateType state = CustomerStateType.Active;
 
// 这里是想要获得的结果。
string result = string.Empty;
 
// 这就是我向你们展示的部分。
Desc description = state.GetAttribute<Desc>();
if (description != null)
    result = description.Value;

我这里对于密集调用还有一个解决方案,正是针对于文章开始所说的列表中的枚举转换。上述扩展方法的调用虽然会损失一些效率,但是可以通过缓存来解决这个问题。先给大家看最终的效果:

private static Dictionary<CustomerStateType, string> _customerState = null;
/// <summary>
/// 获得客户状态字典
/// </summary>
public static Dictionary<CustomerStateType, string> CustomerStates
{
    get
    {
        if (_customerState == null)
            _customerState = Enum<CustomerStateType>.AsEnumerable()
                  .ToDictionary(p => p, p => p.GetAttribute<Desc>().Value);
 
        return _customerState;
    }
}

可以看到这是一个静态属性,放在字典中。想要获取的时候可以通过键获得相应的中文解释。代码中的Enum<>类是我从CoolCode那窃取了一点想法,用于将一个枚举类型转换为IEnumable<>的可迭代序列。其代码如下:

/// <summary>
/// T 类型枚举列表
/// </summary>
/// <typeparam name="T">枚举类型</typeparam>
public class Enum<T> : IEnumerable<T>
{
    #region Business Methods
    /// <summary>
    /// 返回类型为 IEnumerable&lt;T&gt; 的输入。
    /// </summary>
/// <returns>类型为 IEnumerable&lt;T&gt; 的序列。</returns>
    public static IEnumerable<T> AsEnumerable()
    {
        return new Enum<T>();
    }
    #endregion
 
    #region IEnumerable<T> 成员
 
    /// <summary>
    /// 返回一个循环访问集合的枚举数。
    /// </summary>
    /// <returns>可用于循环访问集合的 IEnumerator&lt;T&gt; 。</returns>
    public IEnumerator<T> GetEnumerator()
    {
        return Enum.GetValues(typeof(T))
            .OfType<T>()
            .AsEnumerable()
            .GetEnumerator();
    }
 
    #endregion
 
    #region IEnumerable 成员
 
    /// <summary>
    /// 返回一个循环访问集合的枚举数。
    /// </summary>
    /// <returns>可用于循环访问集合的 IEnumerator 。</returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
 
    #endregion
 
    #region IEnumerable<T> 成员
 
    /// <summary>
    /// 返回一个循环访问集合的枚举数。
    /// </summary>
    /// <returns>
    /// <returns>可用于循环访问集合的 IEnumerator&lt;T&gt; 。</returns>
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return GetEnumerator();
    }
 
    #endregion
}

这个类很简单,就是将枚举类型的所有成员以一个IEnumable的形式展示,但是并没有做额外的工作。但就是这样,使得我们有了强大的扩展功能,比如上面我所做的那个缓存字典。

我还有点不放心的是,文中用到了很多微软提供给我们的Linq扩展方法,不知道大家能明白多少。不过没关系,园子里的LINQ专题可以提供参考。相信这不是什么问题!关于LINQ的东西,我不想过多的发表观点,这里用一句话表明吧:很强大,能让你在.Net平台上走得更远!

当然,这个问题的解决方案肯定不止上面我提到的这两种,聪明的你肯定还有其它的更好的方法,不妨写出来,让大家分享一下。

原文地址:https://www.cnblogs.com/lenic/p/1934209.html