"Decimal" 标志(而不是二进制)- 内置方式?

"Decimal" flags (instead of binary) - built-in way?

我有这个从一些文档中得到的公式,它应该解释“Flags”整数代表什么:

Flags = Spectator * 1 + TemporarySpectator * 10 + PureSpectator * 100 + AutoTarget * 1000 + CurrentTargetId * 10000

我写了这段代码,它能够将一个数字(标志)转换为一些布尔值 + 一个整数,就像在公式中一样:

////////// From Values to Flag //////////

bool Spectator          = true;
bool TemporarySpectator = false;
bool PureSpectator      = true;
bool AutoTarget         = false;
int  CurrentTargetId    = 255;

int Calculate() =>
    (Convert.ToInt32(Spectator)          * 1) +
    (Convert.ToInt32(TemporarySpectator) * 10) +
    (Convert.ToInt32(PureSpectator)      * 100) +
    (Convert.ToInt32(AutoTarget)         * 1000) +
    (Convert.ToInt32(CurrentTargetId)    * 10000);

int Result = Calculate(); // 2550101


////////// From Flag to Values //////////

CurrentTargetId      = Convert.ToInt32(Result / 10000);
AutoTarget           = Convert.ToBoolean((Result - (CurrentTargetId * 10000)) / 1000);
PureSpectator        = Convert.ToBoolean((Result - (CurrentTargetId * 10000) - (Convert.ToInt32(AutoTarget) * 1000)) / 100);
TemporarySpectator   = Convert.ToBoolean((Result - (CurrentTargetId * 10000) - (Convert.ToInt32(AutoTarget) * 1000) - (Convert.ToInt32(PureSpectator) * 100)) / 10);
Spectator            = Convert.ToBoolean((Result - (CurrentTargetId * 10000) - (Convert.ToInt32(AutoTarget) * 1000) - (Convert.ToInt32(PureSpectator) * 100) - (Convert.ToInt32(TemporarySpectator) * 100)) / 1);

Result = Calculate(); // 2550101

如您所见,我的代码也可以执行反向操作 - 将值转换为标志。

Fiddle 我的代码:https://dotnetfiddle.net/ua2wi8

这种操作有什么名字吗?我知道枚举的 FlagsAttribute 类似,但将标志存储为单个位(“二进制数字”)而不是像我的情况那样的十进制数字。

在 C# 中是否有更简单甚至本机的方法来执行此操作?使用模型 class 中的布尔值会更好。

FlagsAttribute for enums 仅控制使用 ToString 时枚举的打印方式,它将在漂亮的 comma-separated 列表中列出所有值。

您尝试做的事情可以通过枚举(最佳)实现,但如果您了解二进制或 2 的幂,也可以通过使用整数来实现。

使用枚举

public enum MyEnum {
    Spectator          = 1 << 0,    // 1 or 2^0
    TemporarySpectator = 1 << 1,    // 2 or 2^1
    PureSpectator      = 1 << 2,    // 4 or 2^2
    AutoTarget         = 1 << 3,    // 8 or 2^3
    CurrentTargetId    = 1 << 4     // 16 or 2^4
}

MyEnum flags =  MyEnum.Spectator | MyEnum.PureSpectator | MyEnum.CurrentTargetId;

////////// Extracting individual values from flags //////////

MyEnum currentTargetId      = flags & MyEnum.CurrentTargetId;
MyEnum autoTarget           = flags & MyEnum.AutoTarget;
MyEnum pureSpectator        = flags & MyEnum.PureSpectator;
MyEnum temporarySpectator   = flags & MyEnum.TemporarySpectator;
MyEnum spectator            = flags & MyEnum.Spectator;

if (flags.HasFlag(MyEnum.AutoTarget))
{
    // Do Stuff
}

if (flags.HasFlag(MyEnum.Spectator))
{
    // Do Stuff
}

使用纯整数

const int spectator            = 0b_0000_0001;    // 1 or 2^0
const int temporarySpectator   = 0b_0000_0010;    // 2 or 2^1
const int pureSpectator        = 0b_0000_0100;    // 4 or 2^2
const int autoTarget           = 0b_0000_1000;    // 8 or 2^3
const int currentTargetId      = 0b_0001_0000;    // 16 or 2^4

int flags =  spectator | pureSpectator | currentTargetId;

////////// Extracting individual values from flags //////////

int currentTargetIdVal      = flags & currentTargetId;
int autoTargetVal           = flags & autoTarget;
int pureSpectatorVal        = flags & pureSpectator;
int temporarySpectatorVal   = flags & temporarySpectator;
int spectatorVal            = flags & spectator;

if ((flags & autoTarget) > 0)
{
    // Do Stuff
}

if ((flags & spectator) > 0)
{
    // Do Stuff
}

C#也允许使用Binary Literals,所以MyEnum也可以这样写:

public enum MyEnum {
    Spectator          = 0b_0000_0001,
    TemporarySpectator = 0b_0000_0010,
    PureSpectator      = 0b_0000_0100,
    AutoTarget         = 0b_0000_1000,
    CurrentTargetId    = 0b_0001_0000
}

您可以将其全部包装在 class 中,并使用模运算符来简化事情:

class DecimalFlags
{
    private int _value = 0;
    public bool Spectator
    {
        get
        {
            return _value % 10 == 1;
        }
        set
        {
            if (value && _value % 10 != 1) _value += 1;
            else if (_value % 10 == 1) _value -= 1;
        }
    }

    public bool TemporarySpectator
    {
        get
        {
            return (_value/10) % 10 == 1;
        }
        set
        {
            if (value && (_value/10) % 10 != 1) _value += 10;
            else if ((_value/10) % 10 == 1) _value -= 10;
        }
    }

    public bool PureSpectator
    {
        get
        {
            return (_value / 100) % 10 == 1;
        }
        set
        {
            if (value && (_value / 100) % 10 != 1) _value += 100;
            else if ((_value / 100) % 10 == 1) _value -= 100;
        }
    }

    public bool AutoTarget
    {
        get
        {
            return (_value / 1000) % 10 == 1;
        }
        set
        {
            if (value && (_value / 1000) % 10 != 1) _value += 1000;
            else if ((_value / 1000) % 10 == 1) _value -= 1000;
        }
    }
    
    public int CurrentTargetId {get;set;}
    
    public static explicit operator int(DecimalFlags df)
    {
        return df._value + df.CurrentTargetId*10000;
    }
    public static explicit operator DecimalFlags(int i)
    {
        var df = new DecimalFlags();
        df.CurrentTargetId = i/10000;
        df._value = i - df.CurrentTargetId*10000;
        return df;
    }
}

然后当你需要将你的值传回游戏时你可以说

MyGameFunction((int)decimalFlags);

或者从游戏中获取值:

int value = GetValueFromGame();
var decimalFlags = (DecimalFlags)value;
bool isSpectator = decimalFlags.Spectator; //etc

您可以创建自己的结构对象来表示标志。下面显示的这个结构将以 8 个字节或 long 的大小存储所有标志 您的 CurrentTaregtId。然后,您可以使用 .Calculate 函数来获取您要查找的十进制值。所有这些标志的值都可以达到 255,而十进制系统的缺点是每个值最多只能容纳 9 才能用作适当的 'flag'.

请记住,此代码尚未测试。因此可能需要 较小的 改动。

[StructLayout(LayoutKind.Explicit)]
public struct MyFlags {

    [FieldOffset(0)]
    public ulong Value;
    
    [FieldOffset(0)]
    public byte Spectator;

    [FieldOffset(1)]
    public byte TemporarySpectator;

    [FieldOffset(2)]
    public byte PureSpectator;

    [FieldOffset(3)]
    public byte AutoTarget;

    [FieldOffset(4)]
    public uint CurrentTargetId;
    
    public MyFlags(in ulong value) {
        Value = value;
    }
    
    public MyFlags(in byte spectator, in byte temporarySpectator, in byte pureSpectator, in byte autoTarget, in uint currentTargetId) {
        Spectator = spectator;
        TemporarySpectator = temporarySpectator;
        PureSpectator = pureSpectator;
        AutoTarget = autoTarget;
        CurrentTargetId = currentTargetId;
    }
    
    public long Calculate()
    {
        return Spectator
            + TemporarySpectator * 10
            + PureSpectator * 100
            + AutoTarget * 1000
            + CurrentTargetId * 10000;
    }
}

/// Usage

MyFlags flags = new MyFlags();
flags.TemporarySpectator = 4;
flags.PureSpectator = 7;
flags.CurrentTargetId = 255;
flags.Calculate();

if (flags.AutoTarget > 0)
{
    // Do stuff
}

以下是我最终的做法,使用手动字符串解析:

public class SpectatorStatus : IFlag
{
    public SpectatorStatus(int value)
    {
        var valueStr = value.ToString("D7");

        const char True = '1';

        CurrentTargetId = Convert.ToInt32(valueStr[..3]);
        AutoTarget = valueStr[3] == True;
        PureSpectator = valueStr[4] == True;
        TemporarySpectator = valueStr[5] == True;
        Spectator = valueStr[6] == True;
    }

    public bool Spectator { get; }
    public bool TemporarySpectator { get; }
    public bool PureSpectator { get; }
    public bool AutoTarget { get; }
    public int CurrentTargetId { get; }

    public override string ToString()
        => (Convert.ToInt32(Spectator)
            + Convert.ToInt32(TemporarySpectator) * 10
            + Convert.ToInt32(PureSpectator) * 100
            + Convert.ToInt32(AutoTarget) * 1000
            + CurrentTargetId * 10000)
            .ToString("D7");
}