C# 将枚举值数组转换为枚举标志

C# 将枚举值数组转换为枚举标志,c#,arrays,enums,C#,Arrays,Enums,我正在尝试以此处指定的方式实现功能: 但是,我尝试将其作为通用方法,用作扩展: public static TEnum? Merge<TEnum>(this IEnumerable<TEnum> values) where TEnum : struct, IConvertible, IComparable, IFormattable { Nullable<TEnum> merged = null;

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

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

    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;
    }
不会编译。我得到的信息是:

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


是否可以编写此泛型方法将枚举值数组转换为标志enum?

这里有几个问题,但最大的问题是泛型不支持运算符,
|
是运算符。您可以通过
对象
对其进行黑客攻击,但随后您就有了拳击。下面是我要做的-它会为每个枚举类型生成一些动态IL(仅一次),并使用它在不装箱的情况下直接执行“或”。请注意,它还使用
0
作为默认返回值(IMO预期的要多得多),并避免显式的
Count()
,因为这可能会造成无法预测的代价,并且会破坏枚举器(您不能保证可以多次枚举数据):

它的作用是:

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

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

      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);
      
              }
          }
      }
      
      使用系统;
      使用System.Collections.Generic;
      使用System.Linq;
      使用System.Linq.Expressions;
      公共静态类扩展
      {
      /// 
      ///用于将标志枚举enum合并为单个值。
      /// 
      公共静态TEnum合并(此IEnumerable值)
      其中TEnum:struct,i可转换
      {
      var类型=类型(TEnum);
      如果(!type.IsEnum)
      抛出新的InvalidOperationException($“{type}不是枚举类型。”);
      返回值.DefaultIfEmpty(默认值(TEnum)).Aggregate(运算符.Or);
      }
      静态类运算符
      {
      私有静态只读懒洋洋;
      私有静态只读懒汉;
      公共静态Func Or=>LazyOr.Value;
      公共静态Func And=>LazyAnd.Value;
      静态运算符()
      {
      var enumType=typeof(T);
      var underType=enumType.GetEnumUnderlyingType();
      var leftParam=Expression.Parameter(枚举类型,“左”);
      var rightParam=Expression.Parameter(enumType,“right”);
      var leftCast=Expression.ConvertChecked(leftParam,underType);
      var rightCast=Expression.ConvertChecked(rightParam,underType);
      Lazy CreateLazyOp(Func opFunc)=>
      新懒惰(()=>
      {
      var op=opFunc(左播、右播);
      var resultCast=Expression.ConvertChecked(op,枚举类型);
      var l=Expression.Lambda(resultCast、leftParam、righParam);
      返回l.Compile();
      });
      LazyOr=CreateLazyOp(表达式.Or);
      LazyAnd=CreateLazyOp(表达式.And);
      }
      }
      }
      
      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);
      }
      
      但如果您不想记住此代码,也可以使用扩展方法:

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

      为什么您需要在此处返回
      TEnum?
      [Flags]
      枚举应该始终有一个
      0
      /
      None
      值-您不就返回它吗?旁白:对序列执行
      Count()
      意味着您要重复它两次-这不是一个好主意-可能会很昂贵,但这甚至不能保证有效——没有一种简单的方法不涉及拳击;复杂的方法就足够了吗?我正在尝试将枚举值数组合并成一个由这些值组成的枚举值,条件是TEnum用[FlagsEnum]标记枚举。我在这里说得通吗?:)检查其他stackoverflow线程上的链接,我正在尝试以通用方式完成相同的操作。问题是,一旦进入泛型域,
      Enum
      的行为实际上会丢失,因此您不能简单地更改或更改值。您能调整方法以返回TEnum吗?Defalt(TEnum)对我来说真的不管用。除此之外,我对DynamicMethod的概念还不熟悉,所以我需要一些时间来理解您刚才所做的:)
      default(TEnum)
      ,带有一个小的
      d
      ,是一个保留的C#关键字-即使对于泛型也应该可以很好地工作。我所做的是使用这样一个事实:静态构造函数只被计算一次,因此
      操作符
      的静态构造函数每个unique
      T
      执行一次。因此,在静态构造函数中,我们创建了一个运行时方法,该方法声明2个参数,将它们读入堆栈,应用“or”(幸运的是,存在一个IL运算符),并返回值,并将该方法的类型化委托存储在
      字段中。然后在我们的
      Merge
      方法中,我们获得该委托并按项执行。@barisputer要清楚:
      default(TEnum)
      实际上只是一种说法,“为
      TEnum
      清空足够的内存,并给我值”-这恰好是我们想要的没有任何标志设置的枚举值。抱歉,我输入了一个错误:)我熟悉默认值(TEnum),我只是想用一个方法从标志中构建一个有效的枚举值,或者如果作为输入提供的值集合是null或空集合,则只返回null。@BarisaPuter坦白地说,我认为“else return null”是一个糟糕且令人困惑的主意,但我将添加一个编辑,以显示它更简单:
      result  = First | Second | Third | Bit40
      result2 = First | Second | Third | Bit40
      result .ToString ("X") -> "0000010000000007"
      result2.ToString ("X") -> "0000010000000007"