3

I want to implement the following method using .NET Standard:

public static void SetFlag<TEnum>(ref TEnum value, TEnum flag)
    where TEnum : Enum

I spend hours in trying to achieve this:

  • Getting the | operator via reflection appears to be impossible for primitive types as enums are.
  • Using dynamic requires referencing an extra package (Microsoft.CSharp.RuntimeBinder), but I would like my library to stay pure .NET Standard conform.

My latest idea was to manually compare TEnum to each valid enum type of {byte, sbyte, short, ushort, int, uint, long, ulong}. But this feels really weird and dirty:

try
{
    var v = (byte)(object)value | (byte)(object)flag;
    value = (TEnum)(object)v;
    return;
}
catch (InvalidCastException) { }

try
{
    var v = (int)(object)value | (int)(object)flag;
    value = (TEnum)(object)v;
    return;
}
catch (InvalidCastException) { }

// ...

throw new NotSupportException($"Unknown enum type {typeof(TEnum)}");

So is this really the only option .NET (Standard) provides here or what I am missing? Looking forward to your hints!

Edit: Not a duplicate of this question; I am using C# 7.3 and the generic Enum constraint.

KnorxThieus
  • 567
  • 8
  • 17
  • @Corak I don't think so as I am using the `System.Enum` constraint. – KnorxThieus Apr 04 '19 at 13:18
  • Yes, I just realised that - I deleted my comment! – bornfromanegg Apr 04 '19 at 13:33
  • 1
    @KnorxThieus - yes, sorry. I fully expected this to be possible. – Corak Apr 04 '19 at 13:34
  • Possible duplicate of [C# Method to combine a generic list of enum values to a single value](https://stackoverflow.com/questions/53636974/c-sharp-method-to-combine-a-generic-list-of-enum-values-to-a-single-value) – madreflection Apr 04 '19 at 16:56
  • The duplicate I've suggested has functioning generic methods for performing various bitwise operations on enums of any type and underlying size without boxing. See the answer by Doctor Jones (which optimizes my solution). – madreflection Apr 04 '19 at 17:00
  • Btw I think you want your constraint to be `struct, Enum` otherwise you'll not be able to use it with just any old enum, but only the actual `Enum` type – pinkfloydx33 Apr 05 '19 at 00:04
  • @pinkfloydx33 You cannot invoke `|` on `struct`s as well. – KnorxThieus Apr 06 '19 at 16:31

1 Answers1

4

It's not the cheapest (everything gets boxed, there's some reflection, etc), but you could always do something like this:

private static void SetFlag<T>(ref T value, T flag) where T : Enum
{
    // 'long' can hold all possible values, except those which 'ulong' can hold.
    if (Enum.GetUnderlyingType(typeof(T)) == typeof(ulong))
    {
        ulong numericValue = Convert.ToUInt64(value);
        numericValue |= Convert.ToUInt64(flag);
        value = (T)Enum.ToObject(typeof(T), numericValue);
    }
    else
    {
        long numericValue = Convert.ToInt64(value);
        numericValue |= Convert.ToInt64(flag);
        value = (T)Enum.ToObject(typeof(T), numericValue);
    }
}

You've still got some repetition, but at least it's restricted to long/ulong. If you can assume that your flags enum members won't have negative values, you can just use:

private static void SetFlag<T>(ref T value, T flag) where T : Enum
{
    ulong numericValue = Convert.ToUInt64(value);
    numericValue |= Convert.ToUInt64(flag);
    value = (T)Enum.ToObject(typeof(T), numericValue);
}
canton7
  • 37,633
  • 3
  • 64
  • 77
  • Thanks for your submission. Why exactly would you prefer this solution to my proposal above? Which approach should be faster? – KnorxThieus Apr 04 '19 at 15:13
  • 2
    It will almost certainly be faster. Throwing an exception is hugely expensive (in relative terms), and your approach might throw quite a few exceptions before it finds the right type (particularly as you start with `byte`). At the very least I'd check `Enum.GetUnderlyingType()`, rather than running the risk of getting an exception. Beyond that, I normally follow the rule that less repetition of code is better. Don't forget that your approach boxes a lot as well. – canton7 Apr 04 '19 at 15:15
  • Thank you. Interestingly, not even Microsoft in its `Enum` implementation differentiates between `ulong` and Co. [1] https://referencesource.microsoft.com/#mscorlib/system/enum.cs,158 – KnorxThieus Apr 06 '19 at 17:14