| ■No86940 (魔界の仮面弁士 さん) に返信
度々ありがとうございます。
> アサーションも省いた手抜き実装で良ければ、下記のように書けます。 > > public static class BitFlagHelper > { > public static void SetFlag<TEnum>(ref TEnum self, TEnum flag) where TEnum : struct => self |= (dynamic)flag; > }
アサーションという言葉自体、初めてでした。 デバッグ時にだけできるのはとても便利ですね! 性善説でdynamicを使うことを想定すると確かに楽に書けますね。
> はい。そして拡張メソッドは、普通の静的メソッドとして使うこともできます。 > > 先の使用例のところで > //「propA.SetFlag(FlagEnumA.Visible);」でも可 > BitFlagHelper.SetFlag(ref propA, FlagEnumA.Visible); > という 2 つの使用例を書いていたかと思いますが、 > 前者が拡張メソッド構文で呼び出した場合で、 > 後者が静的メソッドとして呼び出した場合です。 > > なお、"ref" 付きの拡張メソッドを使用するには、C# 7.2 以上のバージョンが必要です。 > C# のバージョンは、プロジェクトのプロパティの [ビルド] タブにある > [詳細設定] ボタンを押して、「言語バージョン」のドロップダウンから指定できます。
調べてみたところバージョンは C# 6.0 でした。 7.2以上になった時には使えるようになると覚えておこうと思います。 それまでは静的メソッドでやってみます。
> >> System.Diagnostics.Debug.Assert(typeof(TEnum).IsEnum); >>これは型が TEnumかチェックを行い、 > TEnum かのチェックではなく > Enum かのチェックですね。 > 「IsEnum が true で無ければならない」という意図のアサーションです。 > > struct 制約だけだと、 > int n = 0; > BitFlagHelper.SetFlag(ref n, 0); > などを許容できてしまうので、ここでは > 型パラメーター TEnum が列挙型かどうかを検査しています。 > > Debug.Assert の代わりに、NotSupportedException や ArgumentOutOfRangeException を > throw するようにしても良いかと思います。ただ、どうせコンパイル時のチェックではなく > 実行時検査にしかならないので、今回はあえて Debug.Assert を使ってみました。 > この場合、リリースビルドでは検査されなくなります。 > >>違っていたらメッセージボックスで呼び出し履歴を表示ですかね > .NET Core だと、既定の通知はメッセージボックスでは無いですけれどね。 > > アサーションを分かりやすくするために説明文を追加することも出来ます。 > System.Diagnostics.Debug.Assert(typeof(TEnum).IsEnum, "型パラメーター TEnum が列挙型ではありません。:" + typeof(TEnum).FullName);
なるほど、色々と勉強になります。 エラー処理についてもまだ勉強中なので、うまく使い分けられるようにしていきます。
> >> var bits = Convert.ToUInt64(self) | Convert.ToUInt64(flag); >>論理和を使えるように、受け取った変数とフラグをUInt64に変換している? > そのままではビット論理和を求めることができないので、整数型に変換しています。 > > この方法なら、元のビットフラグが > 「public enum FlagEnumA」 > 「public enum FlagEnumA : byte」 > 「public enum FlagEnumA : sbyte」 > 「public enum FlagEnumA : short」 > 「public enum FlagEnumA : ushort」 > 「public enum FlagEnumA : int」 > 「public enum FlagEnumA : uint」 > 「public enum FlagEnumA : long」 > 「public enum FlagEnumA : ulong」 > のいずれであったとしても対処できるかと。 > > もしくは ToUInt64 を使う代わりに、 > var bits = (dynamic)self | flag; > self = bits; > という手抜き実装も可能です。
UInt64だとこんなに広い範囲に対応できるんですね。 この辺りも勉強しておかなければ・・・ dynamicは大変魅力的ですが、使い始めると私はきっと多用してしまうな とちょっと自重するようにしています^^; いつかは使いこなしてみたいですね。
> > この場合、bitsはUInt64として扱われるのでしょうか? > はい。「UInt64値 | UInt64値」という論理演算の結果は、UInt64 型になります。 > > > >>self = (TEnum)System.Enum.ToObject(typeof(TEnum), bits); >>UInt64型になっている bits を TEnum型に変換し、受け取った変数に保存している? > その通りです。ここでは列挙型に戻すために Enum.ToObject を用いています。 > > >>ref や out な引数にはプロパティを渡せないんですね。 >>勉強不足でした。 > .NET Framework 4 以降で、列挙型に「HasFlag メソッド」が追加されましたが、 > その逆に、フラグをセットする機能が用意されなかったのも、 > このあたりに理由があるのかも知れません。 > > >>変数を経由して渡せば、実装は可能なのですね。 > VB だとプロパティを渡せると書きましたが、これは VB のコンパイラが > こうした一時変数を自動的に生成して対応しているようです。
書いて頂いた例についても、説明していただいて理解できたかと思います。 確かに HasFlag メソッド があるのに他のものはないですね。 伺って、なるほど・・・と思いました。
詳しく教えて頂いてありがとうございました。 |