■86940 / ) |
Re[3]: フラグを操作するジェネリックメソッドを作る方法 |
□投稿者/ 魔界の仮面弁士 (1601回)-(2018/04/04(Wed) 16:01:40)
|
■No86937 (MTK さん) に返信 > お時間があれば、少し教えて頂けたらと思います。 関連質問になるので、解決済みフラグを外しておきますね。
> 型制約をstructで指定しておいて、メソッド内で型チェックをする という理屈は理解できていると思います。 アサーションも省いた手抜き実装で良ければ、下記のように書けます。
public static class BitFlagHelper { public static void SetFlag<TEnum>(ref TEnum self, TEnum flag) where TEnum : struct => self |= (dynamic)flag; }
>>public static void SetFlag<TEnum>(ref this TEnum self, TEnum flag) where TEnum : struct > ref this TEnum self というように this を使っているということはこれは後述されている拡張メソッドなのでしょうか? はい。そして拡張メソッドは、普通の静的メソッドとして使うこともできます。
先の使用例のところで //「propA.SetFlag(FlagEnumA.Visible);」でも可 BitFlagHelper.SetFlag(ref propA, FlagEnumA.Visible); という 2 つの使用例を書いていたかと思いますが、 前者が拡張メソッド構文で呼び出した場合で、 後者が静的メソッドとして呼び出した場合です。
なお、"ref" 付きの拡張メソッドを使用するには、C# 7.2 以上のバージョンが必要です。 C# のバージョンは、プロジェクトのプロパティの [ビルド] タブにある [詳細設定] ボタンを押して、「言語バージョン」のドロップダウンから指定できます。
>> 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; という手抜き実装も可能です。
> この場合、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 のコンパイラが こうした一時変数を自動的に生成して対応しているようです。
|
|