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#关键字-即使对于泛型也应该可以很好地工作。我所做的是使用这样一个事实:静态构造函数只被计算一次,因此操作符
的静态构造函数每个uniqueT
执行一次。因此,在静态构造函数中,我们创建了一个运行时方法,该方法声明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"