繁体   English   中英

位域的通用枚举

[英]Generic Enum for bit fields

我想让使用位域更容易,而不必为我使用的每个枚举类型复制按位函数。 我想出了一个在 C++ 中运行良好的接口,但我不确定如何在 C# 中构建它,它与使用 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

但是,我收到一条错误消息, CS0019 Operator '|=' cannot be applied to operands of type 'T' and 'T'

我错过了一些简单的东西还是有替代的高性能设计?

使用当前版本的语言,您可以使用运行时代码生成来实现这些缺少的通用运算符。 就是这样。

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

Unsafe.As不同,此代码应适用于所有枚举,无论其底层 integer 类型如何。

直到语言和运行时都支持 static 接口操作符(希望很快,)没有很好的方法来支持| , &~作为泛型。 现在,如果您知道(并验证)所有枚举都具有特定的数据大小(即问题中的short ),那么您也许可以使用Unsafe.As作为一种懒惰的方式来转换事物而无需任何装箱:

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

但请注意,使用Unsafe意味着您会遇到任何错误:如果您错了(即T不可变short ):可能会发生非常糟糕的事情。

运算符当前不能用于泛型类型。 这是 .NET7/C#11 功能的一部分,称为static virtual members in interfaces 这是作为通用数学更大努力的一部分添加的,如下所述:

您可以在接口中添加 static 抽象成员以定义包含可重载运算符、其他 static 成员和 static 属性的接口。

同时,您可能会利用BitArray Class 之类的东西,并结合将实际泛型值转换为代码中的底层原始类型。

要么,要么您必须迁移到 C#11 预览版并使用它提供的接口之一,特别是IBitwiseOperators<TSelf,TOther,TResult>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM