将枚举值的通用列表组合为单个值的 C# 方法

And*_*lyn 5 c#

我想传递一个IEnumerable<T>枚举值(枚举具有 Flags 属性)并返回聚合值。下面的方法有效,但前提是枚举使用默认Int32类型。如果它使用byteInt64它将不起作用。

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)

谢谢安德鲁

mad*_*ion 9

此解决方案将转换内联到基础类型并返回到表达式中的枚举类型。

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仍然是可行的方法。


Rei*_*ica 5

@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 的同一个实例上,您就不会招致多次编译的性能损失。