将枚举值数组转换为标记枚举 [英] Converting array of enum values to flags enum
问题描述
我正在尝试以此处指定的方式来实现功能:
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屋!