将枚举值数组转换为标记枚举 [英] Converting array of enum values to flags enum

查看:108
本文介绍了将枚举值数组转换为标记枚举的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试以此处指定的方式来实现功能:

I am trying to implement functionality in a way that it was specified here:

具体解决方案

但是,我正在尝试将其用作扩展的通用方法:

However, I'm trying to do it as generic method to be used as an extension:

    public static TEnum? Merge<TEnum>(this IEnumerable<TEnum> values)
        where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        Nullable<TEnum> merged = null;
        if (values == null || values.Count() == 0)
            return null;

        foreach(TEnum value in values)
        {
            if (merged == null)
                merged = value;
            else
            {
                merged = merged | value;
            }
        }
        return merged;
    }

问题是这行:

merged = merged | value;

将无法编译.我收到的消息是:

Will not compile. Message I'm getting is:

操作员"|"不能应用于类型为'TEnum?'的操作数和"TEnum".

是否可以编写将枚举值数组转换为标记枚举的通用方法?

Is it possible to write this generic method that will convert array of enum values to flags enum?

推荐答案

这里有两个问题,但最大的问题是泛型不支持运算符-和 | 是一个运算符.您可以通过 object 对其进行破解,但随后需要装箱.这就是我要做的-它会为每个枚举类型生成一个动态IL(仅一次),并使用它来执行直接的或"操作而无需装箱.请注意,它还使用 0 作为默认返回值(远远超出了IMO的要求),并避免了显式的 Count(),因为这样做可能会导致不可预期的代价,并且可能会破坏枚举数(您不能保证可以多次枚举数据):

There are a couple of issues here, but the biggest is that generics does not support operators - and | is an operator. You can hack around it via object, but then you have boxing. Here's what I would do - it generates some dynamic IL per-enum-type (once only), and uses that to do a direct "or" without boxing. Note that it also uses 0 for the default return (far more expected, IMO), and avoids an explicit Count(), as that can be unpredictably expensive, and can break the enumerator (you can't guarantee that you can enumerate data more than once):

using System;
using System.Collections.Generic;
using System.Reflection.Emit;

public static class EnumUtils
{
    public static TEnum Merge<TEnum>(this IEnumerable<TEnum> values)
        where TEnum : struct
    {
        TEnum merged = default(TEnum);
        if (values != null)
        {
            var or = Operator<TEnum>.Or;
            foreach (var value in values)
            {
                merged = or(merged, value);
            }
        }
        return (TEnum)(object)merged;
    }
    static class Operator<T>
    {
        public static readonly Func<T, T, T> Or;

        static Operator()
        {
            var dn = new DynamicMethod("or", typeof(T),
                new[] { typeof(T), typeof(T) }, typeof(EnumUtils));
            var il = dn.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Or);
            il.Emit(OpCodes.Ret);
            Or = (Func<T, T, T>)dn.CreateDelegate(typeof(Func<T, T, T>));
        }

    }
}
static class Program {

    [Flags]
    public enum Foo
    {
        None = 0, A = 1,  B =2, C = 4
    }
    static unsafe void Main()
    {
        var merged = EnumUtils.Merge(new[] { Foo.A, Foo.C });

    }
}


如果对于空或空"情况,如果您真的必须返回 null ,则可以使用以下调整-但我要强调:IMO这是一个不正确实现-对于这种情况,简单地返回 0 (又名 default(TEnum))会更正确.


if you really must return null for the "null or empty" case, then you could use the following tweak - but I emphasize: IMO this is an incorrect implementation - it would be more correct to simply return 0 (aka default(TEnum)) for this scenario.

public static TEnum? Merge<TEnum>(this IEnumerable<TEnum> values)
    where TEnum : struct
{
    if (values == null) return null;
    using (var iter = values.GetEnumerator())
    {
        if (!iter.MoveNext()) return null;
        TEnum merged = iter.Current;
        var or = Operator<TEnum>.Or;
        while(iter.MoveNext())
        {
            merged = or(merged, iter.Current);
        }
        return merged;
    }
}

这是什么:

  • 检查空序列是否短路
  • 获取序列迭代器,然后尝试读取一个值-如果不存在,则短路
  • 使用当前(第一个)值作为种子,并获取运算符
  • 重复序列,依次应用运算符
  • 返回组合值

这篇关于将枚举值数组转换为标记枚举的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆