我想传递一个IEnumerable<T>枚举值(枚举具有 Flags 属性)并返回聚合值。下面的方法有效,但前提是枚举使用默认Int32类型。如果它使用byte或Int64它将不起作用。
public static T ToCombined<T>(this IEnumerable<T> list) where T : struct
{
if (!typeof(T).IsEnum)
throw new ArgumentException("The generic type parameter must be an Enum.");
var values = list.Select(v => Convert.ToInt32(v));
var result = values.Aggregate((current, next) => current | next);
return (T)(object)result;
}
Run Code Online (Sandbox Code Playgroud)
我知道我可以获得基础类型:
Type enumType = typeof(T);
Type underlyingType = Enum.GetUnderlyingType(enumType);
Run Code Online (Sandbox Code Playgroud)
但我不知道如何在方法中使用它。我如何制作扩展方法,以便它可以处理enums具有 flags 属性的任何列表?
更好,但可能是非常大的 UInts 的问题
public static T ToCombined<T>(this IEnumerable<T> list) where T : struct
{
if (!typeof(T).IsEnum)
throw new ArgumentException("The generic type parameter must be an Enum.");
var values = list.Select(v => Convert.ToInt64(v));
var result = values.Sum();
var underlyingType = Enum.GetUnderlyingType(typeof(T));
return (T)Convert.ChangeType(result, underlyingType);
}
Run Code Online (Sandbox Code Playgroud)
谢谢安德鲁
此解决方案将转换内联到基础类型并返回到表达式中的枚举类型。
public static T ToCombined<T>(this IEnumerable<T> list)
where T : Enum
{
Type underlyingType = Enum.GetUnderlyingType(typeof(T));
var currentParameter = Expression.Parameter(typeof(T), "current");
var nextParameter = Expression.Parameter(typeof(T), "next");
Func<T, T, T> aggregator = Expression.Lambda<Func<T, T, T>>(
Expression.Convert(
Expression.Or(
Expression.Convert(currentParameter, underlyingType),
Expression.Convert(nextParameter, underlyingType)
),
typeof(T)
),
currentParameter,
nextParameter
).Compile();
return list.Aggregate(aggregator);
}
Run Code Online (Sandbox Code Playgroud)
请注意,我使用了 C# 7.3Enum类型约束。如果您不使用 C# 7.3,检查struct约束IsEnum仍然是可行的方法。
@madreflection 的回答很好,但是每次调用该方法时它都会编译表达式,这会给您带来显着的性能损失。
编译表达式的优点是,如果你缓存结果委托,与反射相比,你最终不会有性能损失。错过这个机会似乎很遗憾,所以我根据他的回答做了以下内容。
public class GenericBitwise<TFlagEnum> where TFlagEnum : Enum
{
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _and = null;
private readonly Func<TFlagEnum, TFlagEnum> _not = null;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _or = null;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _xor = null;
public GenericBitwise()
{
_and = And().Compile();
_not = Not().Compile();
_or = Or().Compile();
_xor = Xor().Compile();
}
public TFlagEnum And(TFlagEnum value1, TFlagEnum value2) => _and(value1, value2);
public TFlagEnum And(IEnumerable<TFlagEnum> list) => list.Aggregate(And);
public TFlagEnum Not(TFlagEnum value) => _not(value);
public TFlagEnum Or(TFlagEnum value1, TFlagEnum value2) => _or(value1, value2);
public TFlagEnum Or(IEnumerable<TFlagEnum> list) => list.Aggregate(Or);
public TFlagEnum Xor(TFlagEnum value1, TFlagEnum value2) => _xor(value1, value2);
public TFlagEnum Xor(IEnumerable<TFlagEnum> list) => list.Aggregate(Xor);
public TFlagEnum All()
{
var allFlags = Enum.GetValues(typeof(TFlagEnum)).Cast<TFlagEnum>();
return Or(allFlags);
}
private Expression<Func<TFlagEnum, TFlagEnum>> Not()
{
Type underlyingType = Enum.GetUnderlyingType(typeof(TFlagEnum));
var v1 = Expression.Parameter(typeof(TFlagEnum));
return Expression.Lambda<Func<TFlagEnum, TFlagEnum>>(
Expression.Convert(
Expression.Not( // ~
Expression.Convert(v1, underlyingType)
),
typeof(TFlagEnum) // convert the result of the tilde back into the enum type
),
v1 // the argument of the function
);
}
private Expression<Func<TFlagEnum, TFlagEnum, TFlagEnum>> And()
{
Type underlyingType = Enum.GetUnderlyingType(typeof(TFlagEnum));
var v1 = Expression.Parameter(typeof(TFlagEnum));
var v2 = Expression.Parameter(typeof(TFlagEnum));
return Expression.Lambda<Func<TFlagEnum, TFlagEnum, TFlagEnum>>(
Expression.Convert(
Expression.And( // combine the flags with an AND
Expression.Convert(v1, underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum)
Expression.Convert(v2, underlyingType)
),
typeof(TFlagEnum) // convert the result of the AND back into the enum type
),
v1, // the first argument of the function
v2 // the second argument of the function
);
}
private Expression<Func<TFlagEnum, TFlagEnum, TFlagEnum>> Or()
{
Type underlyingType = Enum.GetUnderlyingType(typeof(TFlagEnum));
var v1 = Expression.Parameter(typeof(TFlagEnum));
var v2 = Expression.Parameter(typeof(TFlagEnum));
return Expression.Lambda<Func<TFlagEnum, TFlagEnum, TFlagEnum>>(
Expression.Convert(
Expression.Or( // combine the flags with an OR
Expression.Convert(v1, underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum)
Expression.Convert(v2, underlyingType)
),
typeof(TFlagEnum) // convert the result of the OR back into the enum type
),
v1, // the first argument of the function
v2 // the second argument of the function
);
}
private Expression<Func<TFlagEnum, TFlagEnum, TFlagEnum>> Xor()
{
Type underlyingType = Enum.GetUnderlyingType(typeof(TFlagEnum));
var v1 = Expression.Parameter(typeof(TFlagEnum));
var v2 = Expression.Parameter(typeof(TFlagEnum));
return Expression.Lambda<Func<TFlagEnum, TFlagEnum, TFlagEnum>>(
Expression.Convert(
Expression.ExclusiveOr( // combine the flags with an XOR
Expression.Convert(v1, underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum)
Expression.Convert(v2, underlyingType)
),
typeof(TFlagEnum) // convert the result of the OR back into the enum type
),
v1, // the first argument of the function
v2 // the second argument of the function
);
}
}
Run Code Online (Sandbox Code Playgroud)
ToCombined然后,您的方法将被以下重载替换:
var genericBitwise = new GenericBitwise<FlagType>();
var combinedAnd = genericBitwise.And(new[] { FlagType.First, FlagType.Second, FlagType.Fourth });
var combinedOr = genericBitwise.Or(new[] { FlagType.First, FlagType.Second, FlagType.Fourth });
Run Code Online (Sandbox Code Playgroud)
只要您挂在 GenericBitwise 的同一个实例上,您就不会招致多次编译的性能损失。