2

Edit: This post is not a duplicate, those other solutions do not work in allowing me to use the bitwise operators.

I want to do something like this:

public static class EnumExt
{
    public static enum AddFlag(this enum e, enum flag) => e |= flag;
}

So I can use it like this:

Color color = Red;
color.AddFlag(Color.Dark);

It's just a bit easier to read. And I want this single method to work for all enum values, instead of making an override for each one I plan on working with. Problem is, that code won't work because enum isn't a type like int is. I tried doing something with generics:

public static class EnumExt
{
    public static T AddFlag<T>(this T e, T flag) 
        where T : Enum      // Can't constrain to Enum
    {
        return e = e | flag;
    }
}

and this:

public static T AddFlag<T>(this T e, T flag) 
    where T : struct
{
    if (!(typeof(T).IsEnum)) return default; 

    return e |= flag;      // Operator '|' cannot be applied to T
}

Which has two problems. Because it's a struct this means this method will show up for int values. I'm using struct only because it's the closest constraint to Enum. I exit the method if it's not an Enum, but that still doesn't let the compiler know that e is an enum, even though it should always be at this point.

Also tried using where T : int and using casting, but int is an invalid constraint.

Is there anyway this can be done?

EDIT: I have tried the two answers that have been suggested as solving the problem, but they do not. The one with IEnumConstraint doesn't work because it says my enum does not inherit from it, and neither of the answer allows me to actually do return e |= flag;

// Doesn't work because C# won't allow bitwise operators on generic types 
// Because the constraint is still vague enough for non-enum values to slip through
public static T AddFlag<T>(this T e, T flag)
    where T : struct, IConvertible

// Doesn't work because enums don't inherit from IEnumConstraint
// Same as above
public static T AddFlag<T>(this T e, T flag)
    where T : struct, IEnumConstraint

My guess is that, even if these did limit to only Enum values, it is still possible for some other class to inherit from IEnumConstraint and IConvertible therefore the bitwise operators won't work because it's still not a guarantee that operation will be available for T.

It seems the only real solution is in C# 7.3, where they allow you to use System.Enum as a constraint.

AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
  • 1
    Possible duplicate of [Create Generic method constraining T to an Enum](https://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum) – Xiaoy312 May 15 '18 at 21:12
  • https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#enum-constraints – mjwills May 15 '18 at 22:44
  • What version of VS 2017 do you have installed? Is it the latest preview? – mjwills May 16 '18 at 00:09
  • Possible duplicate of [Anyone know a good workaround for the lack of an enum generic constraint?](https://stackoverflow.com/questions/7244/anyone-know-a-good-workaround-for-the-lack-of-an-enum-generic-constraint) – mjwills May 16 '18 at 01:02
  • 1
    Does https://stackoverflow.com/a/27657447/34092 help? – mjwills May 16 '18 at 05:51
  • "It's just a bit easier to read" - except for anyone *familiar* with the C# language who'll be wondering why the second line of your example isn't just `color = color | Color.Dark` - i.e. the *standard* way of doing this and *not any longer to type*. – Damien_The_Unbeliever May 16 '18 at 06:17
  • Funny, because when I see `color = color | Color.Dark` I'm wondering why it isn't just `color |= Color.Dark`. – AustinWBryan May 16 '18 at 06:24
  • Ah, did you already try? I feel like that defeats a bit of the point. I mean, at least it'll prevent other people from throwing in the wrong type and me having to throw an exception. Am I at least able to cast directly to `int` wihtout having to go through `object`? – AustinWBryan May 16 '18 at 06:25

2 Answers2

2

One possible improvement to AustinWBryan's solution is:

internal static class EnumExtensions
{
    public static bool IsDefinedAndNotDefault<T>(this T value)
        where T : Enum
    {
        return Enum.IsDefined(typeof(T), value) && !value.Equals(default(T));
    }
}
Marco Thomazini
  • 378
  • 3
  • 10
1

Was able to figure it out. I have to cast from T to object then to int, use the bitwise operators on the int values, then reverse it to get the result back.

@mjwills pointed out that C# 7.3 won't be fixing the issue of casting. All it'll fix is the constraint and I'll be able to remove the throw exception.

public static T AddFlag<T>(this ref T e, T flag)
    where T : struct, IConvertible
{
    if (!(typeof(T).IsEnum)) throw new ArgumentException("Value must be an enum");

    int added = (int)(object)e | (int)(object)flag;

    e = (T)(object)added;

    return e;
}
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42