tag:crieit.net,2005:https://crieit.net/tags/%E3%83%A9%E3%83%A0%E3%83%80%E5%BC%8F/feed 「ラムダ式」の記事 - Crieit Crieitでタグ「ラムダ式」に投稿された最近の記事 2021-11-17T20:22:26+09:00 https://crieit.net/tags/%E3%83%A9%E3%83%A0%E3%83%80%E5%BC%8F/feed tag:crieit.net,2005:PublicArticle/17767 2021-11-17T20:22:26+09:00 2021-11-17T20:22:26+09:00 https://crieit.net/posts/if-expression-in-csharp 【C#】え!!C#でif式を?できらぁ! <p>タイトルは正確ではありません。すいません。if式っぽく書ける条件判定関数的なものです。if式っぽく書きたかったので作ってみました。</p> <p>C#の<code>if</code>は文であって式ではありません。式ではないので</p> <pre><code class="csharp">var name = "ほげほげ君"; // こうは書けない var exName1 = if (name.EndsWith("君")) { return name; } else { return name + "君"; } // こう書く var exName2 = ""; if (name.EndsWith("君")) exName = name; else exName2 = name + "君"; </code></pre> <p>みたいに書かなければいけません。人によるかと思いますが、これ読みにくくないですか?<code>exName2</code>への代入が2箇所あるので読んでて鬱陶しいです。何度もこういうコードが出てくると嫌です。</p> <p>このくらいのコードであれば三項演算子で解決するという方法もあるでしょう。</p> <pre><code class="csharp">var name = "ほげほげ君"; var newName = name.EndsWith("君") ? name : name + "君"; </code></pre> <p>これであれば代入は1箇所ですし分かりやすいですね。しかし、次のようなケースではどうでしょう。</p> <pre><code class="csharp">// 三項演算子 var newName = name.EndsWith("さん") ? Regex.Replace(name, "さん$", "君") : (name.EndsWith("君") ? name : name + "君"); // if文 var newName = ""; if (name.EndsWith("さん")) newName = Regex.Replace(name, "さん$", "君"); else if (name.EndsWith("君")) newName = name; else newName = name + "君"; </code></pre> <p>最後が"さん"であれば"君"に変換するという場合は、三項演算子を使えば分かりにくくなり、<code>if</code> <code>else if</code> <code>else</code>では悪戯に長くなりますし結局代入箇所が分散していますね。もっとこう、スパっと書けないものでしょうか。ということでif式っぽいものを作りました。</p> <pre><code class="csharp">// ex.1 var newName1 = Ext.If(name.EndsWith("さん"), Regex.Replace(name, "さん$", "君")) .ElseIf(!name.EndsWith("君"), name + "君") .Else(name); </code></pre> <p>あるいは</p> <pre><code class="csharp">// ex.2 var newName2 = name.EndsWith("さん").Then(Regex.Replace(name, "さん$", "君")) .ElseIf(!name.EndsWith("君"), name + "君") .Else(name); </code></pre> <p>上記のように書くためのメソッドです。条件に対して欲しい結果をメソッドチェーンで書いていけるのでなんとなく読みやすいと思います。</p> <p>まずは<strong>ex.1</strong>にある<code>If</code> <code>ElseIf</code> <code>Else</code>というメソッドがこちら。</p> <pre><code class="csharp">public static Tuple<bool, T> If<T>(bool term, T value) { if (term) { // 条件が成立する場合はvalue return new Tuple<bool, T>(true, value); } else { // 条件が成立しない場合はT型のdefault return new Tuple<bool, T>(false, default); } } public static Tuple<bool, T> ElseIf<T>(this Tuple<bool, T> prev, bool term, T value) { if (prev.Item1) { // 前の式が成立している場合はそれをそのまま返す return prev; } else if (term) { // 条件が成立する場合はvalue return new Tuple<bool, T>(true, value); } else { // 条件が成立しない場合はT型のdefault return new Tuple<bool, T>(false, default); } } public static T Else<T> Else(this Tuple<bool, T> prev, T value) { if (prev.Item1) { // 前の式が成立する場合は、その値を取り出して返す return prev.Item2; } else { // 前の式が成立しない場合は規定値としてvalueを返す return value; } } </code></pre> <p>if式を実現するにあたり、前の式の計算結果と値を受け取って引き継ぎつつ、最終的には値のみを返すという処理をする必要があります。そのため、<code>Tuple<bool, T></code>でそれらの情報を受け渡し、最終的に<code>Else</code>で<code>T</code>型の値部分のみを返しています。</p> <p><code>ElseIf</code>と<code>Else</code>では<code>Tuple<bool, T></code>の拡張メソッドを使用しています。なので、このメソッドは<code>static</code>なクラスで宣言する必要があります。</p> <p>続いて<strong>ex.2</strong>にある<code>Then</code>について。<strong>ex.2</strong>の<code>ElseIf</code>と<code>Else</code>は<strong>ex.1</strong>のものと同様です。</p> <pre><code class="csharp">public static Tuple<bool, T> Then<T>(this bool term, T value) { if (term) { // 条件が成立する場合はvalue return new Tuple<bool, T>(true, value); } else { // 条件が成立しない場合はT型のdefault return new Tuple<bool, T>(false, default); } } </code></pre> <p>これも単純に<strong>ex.1</strong>の<code>If</code>の引数<code>term</code>を、引数ではなくメソッドチェーン的に取れるように<code>this</code>キーワードで拡張したメソッドになります。<code>this</code>キーワードにしている引数は明示的に指定することも可能なので、なんなら<strong>ex.1</strong>の<code>If</code>メソッドをこの<code>Then</code>メソッドで代用することも可能です。(条件を指定するならIf的な名前がいいと思って別に定義しました)</p> <p><code>ElseIf</code>は必要に応じて省略することも、複数記述することもできます。</p> <pre><code class="csharp">var point = GetPoint(); var score = Ext.If(point == 100, "S") .ElseIf(point >= 80, "A") .ElseIf(point >= 60, "B") .ElseIf(point >= 40, "C") .ElseIf(point >= 30, "D") .Else("E"); </code></pre> <p>このように書けます。if文でちまちま書いたり三項演算子でネストして書いていくよりはるかにわかりやすいと思います。</p> <p><code>Func</code>や<code>Action</code>を受け取るよう拡張すれば、ラムダ式などを使ってより柔軟な表現が出来るようになると思います。たとえばActionだと、</p> <pre><code class="csharp">public static bool Then(this bool term, Action action) { if (!term) return false; action(); return true; } public static bool ElseIf(this bool previous, bool term, Action action) { if (previous) return true; else if (!term) return false; action(); return true; } public static void Else(this bool previous, Action action) { if (previous) return; action(); } // *** var point = GetPoint(); (point == 100).Then(() => Console.WriteLine("S")) .ElseIf((point >= 80), () => Console.WriteLine("A")) .ElseIf((point >= 60), () => Console.WriteLine("B")) .ElseIf((point >= 40), () => Console.WriteLine("C")) .ElseIf((point >= 30), () => Console.WriteLine("D")) .Else(() => Console.WriteLine("E")); </code></pre> <p>こんな感じで書けます。</p> <p>ジェネリクスを使用していますが制約などはつけていませんし、より最適なコードもあるかもしれません。ある程度拡張性はあると思いますが、もっといいやり方あるよ!などがあれば、是非教えてください。</p> あぱしょに