【问题标题】:Converting array of enum values to flags enum将枚举值数组转换为标志枚举
【发布时间】:2014-06-13 11:06:11
【问题描述】:

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

Specific solution

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

    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;

不会编译。我收到的消息是:

运算符“|”不能应用于“TEnum?”类型的操作数?和“TEnum”。

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

【问题讨论】:

  • 为什么需要在这里返回TEnum?[Flags] 枚举应该总是有一个 0/None 值 - 你不会直接返回吗?
  • 除此之外:在序列上执行Count() 意味着您要对其进行两次迭代——这不是一个好主意——它可能很昂贵,但甚至不能保证工作
  • 没有不涉及拳击的简单方法;复杂的方法就足够了吗?
  • 我正在尝试将枚举值数组合并为一个由这些值组成的枚举值,条件是 TEnum 是用 [FlagsEnum] 标记的枚举。我在这里有意义吗? :) 检查其他 stackoverflow 线程上的链接,我正在尝试以通用方式完成相同的操作。
  • 问题是一旦你进入泛型领域,Enum 的行为实际上会丢失,因此你不能简单地 OR 值。

标签: c# arrays enums


【解决方案1】:

这里有几个问题,但最大的问题是泛型不支持运算符 - 而| 是一个运算符。你可以通过object 破解它,但是你有拳击。这就是我要做的——它会为每个枚举类型生成一些动态的 IL(仅一次),并使用它来执行直接“或”而无需装箱。请注意,它还使用0 作为默认返回(IMO 更多预期),并避免明确的Count(),因为这可能会非常昂贵,并且可能会破坏枚举器(你不能保证你可以枚举数据不止一次):

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 或空”的情况返回 null,那么您可以使用以下调整 - 但我强调:IMO 这是一个不正确 实现 - 对于这种情况,简单地返回 0(又名 default(TEnum))会更正确。

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

这是做什么的:

  • 检查空序列,短路
  • 获取序列迭代器,并尝试读取一个值 - 如果没有则短路
  • 使用当前(第一个)值作为我们的种子,并获取运算符
  • 迭代序列,依次应用运算符
  • 返回组合值

【讨论】:

  • 你可以调整方法返回 TEnum 吗? Defalt(TEnum) 并没有真正为我工作。除此之外,我是 DynamicMethod 概念的新手,所以我需要一些时间来理解你刚刚做了什么:)
  • default(TEnum),带有一个小的 d,是一个保留的 C# 关键字——即使对于泛型也应该可以正常工作。我所做的是使用静态构造函数只评估一次这一事实,因此Operator&lt;T&gt; 的静态构造函数对每个唯一的T 执行一次。因此,在静态构造函数中,我们创建了一个运行时方法,它声明了 2 个参数,将它们读入堆栈,应用“或”(幸运的是,存在 IL 运算符),并返回值 - 并将类型化委托存储在该方法的Or 字段。然后在我们的Merge 方法中,我们获取该委托并按项目执行它。
  • @BarisaPuter 要清楚:default(TEnum) 实际上只是一种说法“为TEnum 清零足够的内存,并给我价值” - 碰巧,正是我们想要一个没有设置任何标志的枚举值。
  • 对不起,我打错了 :) 我对默认值(TEnum)很熟悉,我只是想使用一种方法来从标志中构建一个有效的枚举值,或者只返回 null相反,如果作为输入提供的值集合是 null 或空集合。
  • @BarisaPuter 坦率地说,我认为“else return null”是一个糟糕且令人困惑的想法,但我会添加一个编辑来表明这一点
【解决方案2】:

Linq 1 行,不需要辅助方法:

var result = enumArray.Aggregate ((TestEnum)0, (a, b) => a | b);

但是如果你不想记住这段代码,扩展方法也是可以的:

private static T Merge<T> (this T[] i_enumValues)
// - or -
// private static T Merge<T> (params T[] i_enumValues)
  where T : Enum
{
  var type = typeof (T);
  if (!type.IsDefined (typeof (FlagsAttribute), false))
    throw new ArgumentException ($"The given enum type '{type}' does not have the {nameof (FlagsAttribute)}.");

  var zero = (T)Convert.ChangeType (0, Enum.GetUnderlyingType (type));

  var result = i_enumValues.Aggregate (zero, (a, b) => (dynamic)a | (dynamic)b);

  return result;
}

概念证明:

[Flags]
public enum TestEnum : ulong
{
  First  = 1   << 0,
  Second = 1   << 1,
  Third  = 1   << 2,
  Fourth = 1   << 3,
  Fifth  = 1   << 4,
  Bit40  = 1ul << 40
}

private static void Main (string[] args)
{
  var enumArray = new[] { TestEnum.First, TestEnum.Second, TestEnum.Third, TestEnum.Bit40 };
  var result    = enumArray.Aggregate ((TestEnum)0, (a, b) => a | b);
  var result2   = Merge (enumArray);
}

输出:

result  = First | Second | Third | Bit40
result2 = First | Second | Third | Bit40
result .ToString ("X") -> "0000010000000007"
result2.ToString ("X") -> "0000010000000007"

【讨论】:

  • 不错的答案。奇怪的是你写了一个扩展方法并且没有使用相关的语法enumArray.Merge()
  • @Askolein:查看我对答案的编辑。该方法首先是private static T Merge&lt;T&gt; (params T[] i_enumValues)。我只是没有更新示例。
【解决方案3】:

我需要这种能力,很高兴找到马克的答案。我尝试使用一些较新的语言功能。这是我想出的:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public static class Extensions
{
    /// <summary>
    /// Intended for merging an enumeration of flags enum into a single value.
    /// </summary>
    public static TEnum Merge<TEnum>(this IEnumerable<TEnum> values)
        where TEnum : struct, IConvertible
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
            throw new InvalidOperationException($"{type} is not an enum type.");

        return values.DefaultIfEmpty(default(TEnum)).Aggregate(Operator<TEnum>.Or);
    }

    static class Operator<T>
    {
        private static readonly Lazy<Func<T, T, T>> LazyOr;
        private static readonly Lazy<Func<T, T, T>> LazyAnd;

        public static Func<T, T, T> Or => LazyOr.Value;
        public static Func<T, T, T> And => LazyAnd.Value;

        static Operator()
        {
            var enumType = typeof(T);
            var underType = enumType.GetEnumUnderlyingType();
            var leftParam = Expression.Parameter(enumType, "left");
            var rightParam = Expression.Parameter(enumType, "right");
            var leftCast = Expression.ConvertChecked(leftParam, underType);
            var rightCast = Expression.ConvertChecked(rightParam, underType);

            Lazy<Func<T, T, T>> CreateLazyOp(Func<Expression, Expression, BinaryExpression> opFunc) =>
            new Lazy<Func<T, T, T>>(() =>
            {
                var op = opFunc(leftCast, rightCast);
                var resultCast = Expression.ConvertChecked(op, enumType);
                var l = Expression.Lambda<Func<T, T, T>>(resultCast, leftParam, rightParam);
                return l.Compile();
            });

            LazyOr = CreateLazyOp(Expression.Or);
            LazyAnd = CreateLazyOp(Expression.And);

        }
    }
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2013-01-17
  • 2019-03-04
  • 1970-01-01
  • 2020-08-21
  • 2021-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多