I want to pass a IEnumerable<T>
of enum values (enum has the Flags attribute) and return the aggregate value. The method below works but only if the enum uses the default Int32
type. If it uses byte
or Int64
it won't work.
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;
}
I know I can get the underlying type:
Type enumType = typeof(T);
Type underlyingType = Enum.GetUnderlyingType(enumType);
but I don't see how to make use of it in the method. How can I make the extension method so it can handle a list of any enums
with the flags attribute?
Better but might be a problem with really big 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);
}
Thanks Andrew
This solution inlines the conversions to the underlying type and back to the enum type in an expression.
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);
}
Note that I've used the C# 7.3 Enum
type constraint. If you're not using C# 7.3, the struct
constraint with the IsEnum
check is still the way to go.
@madreflection's answer is great, but it compiles the expression every time the method is called, which will give you a significant performance hit.
The advantage of compiling expressions, is, if you cache the resulting delegate, you end up with no performance penalty, when compared to reflection. It seemed a shame to miss out on this opportunity, so I made the following, based on his answer.
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
);
}
}
Your ToCombined
method is then replaced by the following overloads:
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 });
As long as you hang onto the same instance of GenericBitwise, you won't incur the performance penalty of multiple compiles.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With