インドカレーファンクラブ

パソコン、カメラ

【C#】EnumのFlagsAttributeについて

C#EnumのFlagsAttributeについて

なにそれ?

FlagsAttribute クラス
列挙体をビット フィールド、つまりフラグのセットとして扱えることを示します。 https://docs.microsoft.com/ja-jp/dotnet/api/system.flagsattribute?view=netcore-3.1

Enumにつける[Flags]という属性
これについての説明

結構勘違いしたまま使っている人が多い印象

本題

属性の有無でどうなるか、コードの動きを見たほうが早い

// 早い・安い・美味い!

public enum Feature : byte
{
    FastDelivery = 1,      // 1
    Reasonable   = 1 << 1, // 2
    Tasty        = 1 << 2  // 4
}

[Flags]
public enum Feature2 : byte
{
    FastDelivery = 1,      // 1
    Reasonable   = 1 << 1, // 2
    Tasty        = 1 << 2  // 4
}
class FlagsAttributeSample
{
    public static void Exec()
    {
        // [Flags]なし
        Console.WriteLine($"Feature.FastDelivery: {Feature.FastDelivery}");
        Feature flgs = Feature.FastDelivery | Feature.Tasty; // 早い・美味い
        Console.WriteLine($"flg: {flgs}");
        foreach (Feature f in Enum.GetValues(typeof(Feature)))
        {
            bool hasFlg = flgs.HasFlag(f);
            Console.WriteLine($"has {f.ToString()} : {hasFlg}");
        }

        Console.WriteLine("------------");

        // [Flags]あり
        Console.WriteLine($"Feature2.FastDelivery: {Feature2.FastDelivery}");
        Feature2 flgs2 = Feature2.FastDelivery | Feature2.Tasty; // 早い・美味い
        Console.WriteLine($"flgs2: {flgs2}");
        foreach (Feature2 f2 in Enum.GetValues(typeof(Feature2)))
        {
            bool hasFlg = flgs2.HasFlag(f2);
            Console.WriteLine($"has {f2.ToString()} : {hasFlg}");
        }

    }
}

結果

Feature.FastDelivery: FastDelivery
flg: 5
has FastDelivery : True
has Reasonable : False
has Tasty : True
------------
Feature2.FastDelivery: FastDelivery
flgs2: FastDelivery, Tasty
has FastDelivery : True
has Reasonable : False
has Tasty : True

ドキュメントを読んでいないと、「Enum[Flags]を付与することで、bit演算とかhasFlag())とか使えるようになる」と思い込みがちだけど実際はそうでもない
上述のコードの通り演算できている

じゃあ何が違うかというとココ

// [Flags]なし
Feature flgs = Feature.FastDelivery | Feature.Tasty;
Console.WriteLine($"flg: {flgs}"); // flg: 5
// [Flags]あり
Feature2 flgs2 = Feature2.FastDelivery | Feature2.Tasty;
Console.WriteLine($"flgs2: {flgs2}"); // flgs2: FastDelivery, Tasty

OR演算子で複数ビットを持った時に、文字列としての表現のされ方が変わる!!!!

嬉しくね~!と僕は思うんだけど、英語をそのまま表示したい場合は嬉しいのかもしれない
因みに列挙子は日本語で定義することもできるので、そうした時は役に立つかもしれない

[Flags]
public enum Feature : byte
{
    早い   = 1,      // 1
    安い   = 1 << 1, // 2
    美味い = 1 << 2  // 4
}

void Main()
{
    var f = Feature.早い | Feature.安い | Feature.美味い;
    Console.WriteLine(f); // 早い, 安い, 美味い
}

参考:
https://stackoverflow.com/questions/8447/what-does-the-flags-enum-attribute-mean-in-c

備考:HasFlag()について

HasFlag()メソッドではボックス化が起こり実行速度がかなり遅いということだったらしいけど、.NET Core2.1で解消されたみたい

var f = Feature.FastDelivery | Feature.Reasonable;
bool b1 = f.HasFlag(Feature.FastDelivery);

// .NET Core2.1より前ではこっちの方が早くてよい
bool b2 = Feature.FastDelivery == (f & Feature.FastDelivery);

参考: