Admir Tuzović
Admir Tuzović

Reputation: 11177

Converting array of enum values to flags enum

I am trying to implement functionality in a way that it was specified here:

Specific solution

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;
    }

Problem is however that this line:

merged = merged | value;

Will not compile. Message I'm getting is:

Operator '|' cannot be applied to operands of type 'TEnum?' and 'TEnum'.

Is it possible to write this generic method that will convert array of enum values to flags enum?

Upvotes: 3

Views: 1279

Answers (3)

Tobias Knauss
Tobias Knauss

Reputation: 3539

1 line in Linq, no need for a helper method:

var result = enumArray.Aggregate ((TestEnum)0, (a, b) => a | b);

But if you don't want to remember this code, an extension method is also possible:

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;
}

Proof of concept:

[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);
}

Output:

result  = First | Second | Third | Bit40
result2 = First | Second | Third | Bit40
result .ToString ("X") -> "0000010000000007"
result2.ToString ("X") -> "0000010000000007"

Upvotes: 3

Ronnie Overby
Ronnie Overby

Reputation: 46490

I needed this ability and was glad to find Marc's answer. I took a stab at using some newer language features. Here's what I came up with:

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);

        }
    }
}

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1064114

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 });

    }
}

Edit: 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;
    }
}

What this does is:

  • check for a null sequence, short-circuit
  • obtain the sequence iterator, and try to read a value - short-circuit if none
  • use the current (first) value as our seed, and obtain the operator
  • iterate the sequence, applying the operator successively
  • return the combined value

Upvotes: 4

Related Questions