前書き
小ネタ。仕事で必要になったので。
既存DBに"1100"みたいな感じの2進数文字列で値が保存されてて、それを読み出すのをスマートにしたかった。
変換ソース
- ParseToEnumFlags関数: 2進数文字列(例:"1010")→Flags付きenum
- ToBinaryString関数:Flags付きenum→2進数文字列("1010")
- 桁数を引数指定できるようにしている
public static class EnumUtils
{
/// <summary>2進数文字列(例:"1001")をFlags付きEnumに変換する</summary>
/// <typeparam name="TEnum">Enumの型</typeparam>
/// <param name="self">2進数文字列</param>
/// <param name="defaultValue">変換失敗時のデフォルト値</param>
/// <returns>Flags付きEnum</returns>
public static TEnum ParseToEnumFlags<TEnum>(this string self, TEnum defaultValue = default(TEnum))
where TEnum : struct
{
try
{
var binInt = Convert.ToInt32(self, 2);
return Enum.TryParse<TEnum>(binInt.ToString(), out var ret) ? ret : defaultValue;
}
catch
{
return defaultValue;
}
}
/// <summary>Enum => 2進数文字列(例:"1010")</summary>
/// <param name="self">The self.</param>
/// <param name="digits">桁数:nullの時AsIs</param>
/// <returns>2進数文字列</returns>
public static string ToBinaryString(this Enum self, int? digits = null)
{
var ret = Convert.ToString(Convert.ToInt32(self), 2);
if (digits == null)
{
return ret;
}
if( ret.Length > digits.Value)
{
// 間引き 例:(ret, digits) = ("1010", 3) => "010"
return ret.Substring(ret.Length - digits.Value, digits.Value);
}
if (ret.Length < digits.Value)
{
// 0埋め 例:(ret, digits) = ("101", 5) => "00101"
return ret.PadLeft(digits.Value, '0');
}
return ret;
}
}
使用例
皆さんご存知の(嘘)プログレッシブ・ロックで使われるキーボード3種の神器のenumをFlagsで定義したものの例。(要:chaining assertion)
[TestClass]
public class EnumUtilsTest
{
/// <summary>プログレのキーボード</summary>
[Flags]
private enum ProgKeyboards
{
None = 0b000,
Mellotron = 0b001,
MiniMoog = 0b010,
HammondOrgan = 0b100,
}
/// <summary>ParseToEnumFlags正常系Test</summary>
[TestMethod]
public void ParseToEnumFlags正常系Test()
{
"000".ParseToEnumFlags<ProgKeyboards>().Is(ProgKeyboards.None);
"001".ParseToEnumFlags<ProgKeyboards>().Is(ProgKeyboards.Mellotron);
"010".ParseToEnumFlags<ProgKeyboards>().Is(ProgKeyboards.MiniMoog);
"100".ParseToEnumFlags<ProgKeyboards>().Is(ProgKeyboards.HammondOrgan);
"011".ParseToEnumFlags<ProgKeyboards>()
.Is(ProgKeyboards.Mellotron | ProgKeyboards.MiniMoog);
"111".ParseToEnumFlags<ProgKeyboards>()
.Is(ProgKeyboards.Mellotron | ProgKeyboards.MiniMoog | ProgKeyboards.HammondOrgan);
}
/// <summary>ParseToEnumFlags正常系Test</summary>
[TestMethod]
public void ParseToEnumFlags異常系Test()
{
"020".ParseToEnumFlags(ProgKeyboards.None).Is(ProgKeyboards.None);
"aaa".ParseToEnumFlags(ProgKeyboards.None).Is(ProgKeyboards.None);
}
/// <summary>ToBinaryStringTest</summary>
[TestMethod]
public void ToBinaryStringTest()
{
ProgKeyboards.None.ToBinaryString().Is("0");
ProgKeyboards.None.ToBinaryString(3).Is("000");
ProgKeyboards.HammondOrgan.ToBinaryString(3).Is("100");
ProgKeyboards.HammondOrgan.ToBinaryString(4).Is("0100");
(ProgKeyboards.Mellotron | ProgKeyboards.MiniMoog | ProgKeyboards.HammondOrgan).ToBinaryString()
.IndexOf("111");
}
}
#後書き
そもそもFlags付きenumは知らない人も要るのであんまり使うべきで無い。ということで結局このライブラリ関数は作ったものの使用しませんでした。Flags付きenumの代わりに普通のenumで定義しておいて、enumの配列という形で読み出すようにしました。その方がコードが全体的にシンプルになりました。
「実現したいことに比べてソースコードが複雑な場合、たとえうまく動いていても方針を見直す」ことはよくやります。予見できないバグを防ぐために。
参照:社会を「脆く」する犯人「フラジリスタ」とは誰だ? 『反脆弱性』が暴く問題の本質
人々の考えとは裏腹に、複雑系には、複雑なシステムも規制も政策も不要だ。「シンプルであればあるほどよい」のだ。複雑化すると、想定外の影響が連鎖的に膨らんでいく。不透明性のせいで、干渉は予測不能な影響をもたらす。“予測不能”な結果について詫びたあと、二次的な影響を正すために別の干渉をする。すると、“予測不能”な反応は枝分かれ的に急増する。しかも、先に進むたびに影響は深刻になっていく。
もちろんFlags付きenumを使うとスマートになるシナリオもあるので使うこともあります。
それでは良いプログラミングライフをノシ