FlagsAttributeとビット演算のちょっとしたレシピ
「ハッカーのたのしみ」はかなりの良書。いまさらFlagsAttributeのレシピ、リターンズ。.NET FrameworkにBitCountくらい標準であってもいいのにね
http://d.hatena.ne.jp/zecl/20100228/p1
※この記事の内容は古いです。上記の記事をあわせて読むことをオススメします。
C#クックブック第3版のレシピ12.12にて、「Flags属性を付与された列挙型の値の有効性の確認」
というレシピが掲載されていますが、今更感が否めないというか激しく既出というか・・・、
C#をそれなりに使いこなしている人にとっては、イマイチの内容ですね。
そんなわけで、Flags属性のビット演算に関して
例えばこんなレシピはどうでしょう。
その1:「Flags属性が付与された列挙型のうち、ビットがいくつ立っているのかを知る」
その2:「Flags属性が付与された列挙型のうち、ビットが立っているものを知る」
以下、コードです。
using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace ClassLibrary1 { public static class Extentions { /// <summary> /// FlagsAttribute属性が付加されたenumについて、 /// ビットが立っている要素数を取得します。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="self"></param> /// <returns></returns> public static int GetEnumFlagsCount<T>(this T self) where T : struct { if (!self.GetType().IsEnum) throw new ArgumentException("emunじゃなきゃだめ"); if (!Attribute.IsDefined(self.GetType(), typeof(FlagsAttribute))) throw new ArgumentException("FlagsAttrebute属性ついてなきゃだめ"); var chars = Convert.ToString(Convert.ToInt32(self), 2); var count = 0; foreach (var m in Regex.Matches(chars, "1")) count++; return count; } /// <summary> /// FlagsAttribute属性が付加されたenumについて、 /// ビットが立っているenumの要素コレクションを取得します。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="self"></param> /// <returns></returns> public static IEnumerable<T> GetEnumFlags<T>(this T self) where T : struct { if (!self.GetType().IsEnum) throw new ArgumentException("emunじゃなきゃだめ"); if (!Attribute.IsDefined(self.GetType(), typeof(FlagsAttribute))) throw new ArgumentException("FlagsAttrebute属性ついてなきゃだめ"); var chars = Convert.ToString(Convert.ToInt32(self), 2); int count = chars.Length; foreach (var c in chars) { var flag = Convert.ToInt32(Math.Pow(2, --count)); if (c == '1') yield return (T)Enum.ToObject(typeof(T), flag); } } } }
実装はなんかダサダサな感じを漂わせていますが、要件は十分に満たしているかと。
もっとかっこいいコードが書けるよという人がいたら、ぜひ教えてください
使ってみるとこんな感じです。
using System; using System.Linq; using ClassLibrary1; namespace ConsoleApplication1 { class Program { [Flags] enum EDocks { Non = 0x00000, Top = 0x00001, Left = 0x00002, Right = 0x00004, Bottom = 0x00008, All = EDocks.Top | EDocks.Left | EDocks.Right | EDocks.Bottom } static void Main(string[] args) { var enum0 = EDocks.Non; var enum1 = EDocks.Top; var enum2 = EDocks.Top | EDocks.Left; var enum3 = EDocks.Top | EDocks.Left | EDocks.Right; var enum4 = EDocks.All; var enum5 = EDocks.Non | EDocks.Left | EDocks.Left | EDocks.Non; Console.WriteLine(enum0.GetEnumFlagsCount()); Console.WriteLine(enum1.GetEnumFlagsCount()); Console.WriteLine(enum2.GetEnumFlagsCount()); Console.WriteLine(enum3.GetEnumFlagsCount()); Console.WriteLine(enum4.GetEnumFlagsCount()); Console.WriteLine(enum5.GetEnumFlagsCount()); Console.WriteLine(); Console.WriteLine(Enum.ToObject(typeof(EDocks), enum4)); Console.WriteLine(); enum4.GetEnumFlags().ToList().ForEach(x => Console.WriteLine(x.ToString())); Console.WriteLine((16).GetEnumFlagsCount()); Console.ReadLine(); } } }
実行結果
0 1 2 3 4 1 All Bottom Right Left Top System.ArgumentException はハンドルされませんでした。 Message="emunじゃなきゃだめ" Source="ClassLibrary1" StackTrace: 場所 ClassLibrary1.Extentions.GetEnumFlagsCount[T](T self) 場所 D:\自作プログラム\FlagsAttrebuteのビットフラグのカウントとか\ConsoleApplication1\ClassLibrary1\Extentions.cs:行 19 場所 ConsoleApplication1.Program.Main(String[] args) 場所 D:\自作プログラム\FlagsAttrebuteのビットフラグのカウントとか\ConsoleApplication1\ConsoleApplication1\Program.cs:行 42 場所 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) 場所 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 場所 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 場所 System.Threading.ThreadHelper.ThreadStart_Context(Object state) 場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 場所 System.Threading.ThreadHelper.ThreadStart() InnerException:
enum以外の値型からも拡張メソッドが呼び出せてしまう点が、腑に落ちない感じはしますが、
そこはIsEnumとAttribute.IsDefinedでチェックして、例外投げるということで妥協。というか、
そもそもメソッド名を見た時点で、常識的に考えてそんな呼び方すんじゃねーよ!という感じではあるんですけど。