match があれば TryParse いらないんじゃないか
Scala の match を真似て C# で色々書いているわけですが、なんかもう TryParse は Obsolete でいいんじゃ、と思えてきました。
例えばですよ、
public static T MatchInt<T>(this string self, Func<int, T> ifMatch, Func<T> ifNotMatch) { int result; return int.TryParse(self, out result) ? ifMatch(result) : ifNotMatch(); } public static T MatchInt<T>(this string self, Func<int, T> ifMatch) { return MatchInt<T>(self, ifMatch, () => default(T)); }
こんな拡張メソッドを用意しておけば、
int result; if (!int.TryParse(str, out result)) { // 何か処理 return hoge; } // 何か処理 return piyo;
なんて書かずに、
return str.MatchInt( i => { /* 何か処理 */ return piyo; }, () => { /* 何か処理 */ return hoge; } );
って式で書けるんですよ。出力引数もいりません。
例えば数値に変換できるなら変換して返し、できないなら -1 を返す、なんてのは
return str.MatchInt(i => i, () => -1);
ですよ。-1 じゃなくて 0 でいいなら、
return str.MatchInt(i => i);
です。
こんなのも用意したとします。
public static void MatchInt(this string self, Action<int> ifMatch, Action ifNotMatch) { MatchInt<object>(self, i => { ifMatch(i); return null; }, () => { ifNotMatch(); return null; }); } public static void MatchInt(this string self, Action<int> ifMatch) { MatchInt(self, ifMatch, () => {}); }
で、int に変換できるなら標準出力に出力したいなんてのは、
str.MatchInt(Console.WriteLine);
こうです!
あとは、これを T4 Template で
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#v3.5" debug="true" hostSpecific="true" #> <#@ output extension=".cs" encoding="UTF-8" #> <#@ Assembly Name="System.dll" #> <#@ Assembly Name="System.Core.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Diagnostics" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Generic" #> <# // TryParseを持っている型を好きなだけ並べるといい var targets = new[] { "int", "double", "DateTime" }; #> using System; public static partial class StringExtension { <# foreach (var target in targets) { var camelCase = target.ToUpper()[0] + target.Substring(1); #> #region <#= target #>への変換 public static T Match<#= camelCase #><T>(this string self, Func<<#= target #>, T> ifMatch, Func<T> ifNotMatch) { <#= target #> result; return <#= target #>.TryParse(self, out result) ? ifMatch(result) : ifNotMatch(); } public static T Match<#= camelCase #><T>(this string self, Func<<#= target #>, T> ifMatch) { return Match<#= camelCase #><T>(self, ifMatch, () => default(T)); } public static void Match<#= camelCase #>(this string self, Action<<#= target #>> ifMatch, Action ifNotMatch) { Match<#= camelCase #><object>(self, converted => { ifMatch(converted); return null; }, () => { ifNotMatch(); return null; }); } public static void Match<#= camelCase #>(this string self, Action<<#= target #>> ifMatch) { Match<#= camelCase #>(self, ifMatch, () => {}); } #endregion <# } #> }
自動生成!
bool みたいに取り得る値の範囲が狭いなら、
public static T TrueFalseOther<T>(this string self, Func<T> truePart, Func<T> falsePart, Func<T> otherPart) { bool result; if (bool.TryParse(self, out result)) return result ? truePart() : falsePart(); return otherPart(); } public static void TrueFalseOther(this string self, Action truePart, Action falsePart, Action otherPart) { TrueFalseOther<object>( () => { truePart(); return null; }, () => { falsePart(); return null; }, () => { otherPart(); return null; }); }
とするのもありでしょう。
return str.TrueFalseOther(() => 1, () => 0, () => -1);
素敵!
・・・と、ここまで来て何ですが、この方法には重大な欠点があります。
それは、これ全部 string の拡張メソッドとして実現しているという点です。
「"".」まで打ったらメソッドがどばー。
それに、何でもかんでも string のメソッドって意味的にどうなの?ってのもあります。
そこに目を瞑れるなら、アリなんじゃないでしょうか?
少なくとも個人的には、式でわかりやすく記述できる範囲が増えるのでアリです。