Home > Net >  Generic Enum for bit fields
Generic Enum for bit fields

Time:08-02

I'd like to make it easier to work with bitfields instead of having to duplicate bitwise functions for each enum type I use. I came up with an interface which works well in C but I'm not sure how to build it in C# that is as performant as doing bitwise operations with an integer.

enum BitFlags : short
{
    F1 = 1 << 0,
    F2 = 1 << 1,
    F3 = 1 << 2,
};

class Flags<T> where T : System.Enum
{
    T val;

    public void SetFlag(T flag) => val |= flag;
    public void ClearFlag(T flag) => val & ~flag;
    public bool IsSet(T flag) => val & flag;
    // ... ClearAll(), IsExclusivelySet(), SetByBitNumber() 
}

// Flags<BitFlags> tst = new Flags<BitFlags>();
// tst.SetFlag(BitFlags.F1 | BitFlags.F3);
// tst.ClearFlag(BitFlags.F1);
// tst.IsSet(BitFlags.F3) returns true
// tst.IsSet(BitFlags.F1) return false

However, I get an error saying CS0019 Operator '|=' cannot be applied to operands of type 'T' and 'T'

Am I missing something simple or are there alternate performant designs?

CodePudding user response:

With the current version of the language, you can use runtime code generation to implement these missing generic operators. Here’s how.

using System.Linq.Expressions;

class Flags<T> where T : Enum
{
    T val;

    static readonly Func<T, T, T> or, andNot;
    // Static constructors only run once per type
    static Flags()
    {
        Type tEnum = typeof( T );
        ParameterExpression a = Expression.Parameter( tEnum, "a" );
        ParameterExpression b = Expression.Parameter( tEnum, "b" );

        Type tInt = tEnum.GetEnumUnderlyingType();
        Expression ai = Expression.Convert( a, tInt );
        Expression bi = Expression.Convert( b, tInt );

        or = Expression.Lambda<Func<T, T, T>>(
            Expression.Convert( Expression.Or( ai, bi ), tEnum ),
            a, b ).Compile();

        andNot = Expression.Lambda<Func<T, T, T>>(
            Expression.Convert( Expression.And( ai, Expression.Not( bi ) ), tEnum ),
            a, b ).Compile();
    }

    public void SetFlag( T flag ) => val = or( val, flag );
    public void ClearFlag( T flag ) => val = andNot( val, flag );
    public bool IsSet( T flag ) => val.HasFlag( flag );
}

Unlike the Unsafe.As this code should work with all enums regardless on their underlying integer type.

CodePudding user response:

Until the language and runtime supports static interface operators (hopefully soon!), there is no great way to support |, & and ~ as a generic. For now, if you know (and validate!) that all of your enums are of a specific data size (i.e. short in the question), then you could perhaps use Unsafe.As as a lazy way of converting things without any boxing:

public void SetFlag(T flag)
    => Unsafe.As<T, short>(ref val) |= Unsafe.As<T, short>(ref flag);

but note that use of Unsafe means any errors are on you: if you're wrong (i.e. the T is not blittable to short): very bad things could happen.

CodePudding user response:

Operators cannot be used currently with generic types. This is coming as part of a .NET7/C#11 feature called static virtual members in interfaces. This is being added as part of a bigger effort for generic math as explained here:

You can add static abstract members in interfaces to define interfaces that include overloadable operators, other static members, and static properties.

In the meantime, you could probably leverage something like the BitArray Class combined with casting the actual generic values to their underlying primitive types in your code.

Either that, or you'll have to migrate to C#11 preview and use one of the interfaces it provides, in particular IBitwiseOperators<TSelf,TOther,TResult>.

  • Related