tag:crieit.net,2005:https://crieit.net/tags/if%E6%96%87/feed 「if文」の記事 - Crieit Crieitでタグ「if文」に投稿された最近の記事 2021-11-17T20:22:26+09:00 https://crieit.net/tags/if%E6%96%87/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> あぱしょに tag:crieit.net,2005:PublicArticle/17724 2021-10-26T20:31:27+09:00 2021-10-26T20:31:27+09:00 https://crieit.net/posts/about-curly-braces-of-if-statement-in-csharp C#のif文の波括弧{}について <p>C#では<code>if</code>などの<code>{}</code>を省略<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>して書けます。たとえば</p> <pre><code class="csharp">// これを if (girl.IsBoyish) { Console.Write("かわいいね"); } // こう if (girl.IsBoyish) Console.Write("かわいいね"); </code></pre> <p>こんな感じで書ける訳ですね。タイプ量が減りますし個人的にはこのスタイルが好きです。</p> <p>ただしあくまで<code>if</code>の後が単一の処理の場合なので、<code>girl.IsBoyish</code>が<code>true</code>の場合は「(1)コンソール出力」と「(2)ボーイッシュリストに入れる」ということをしたい場合に</p> <pre><code class="csharp">if (girl.IsBoyish) Console.Write("かわいいね"); boyishGirls.Add(girl); </code></pre> <p>こういう風には書けません。C#はインデントをブロックとして解釈しないので、このコードは下記の様に解釈されます。</p> <pre><code class="csharp">if (girl.IsBoyish) { Console.Write("かわいいね"); } boyishGirls.Add(girl); </code></pre> <p>この場合、「(1)コンソール出力」は<code>girl.IsBoyish</code>が<code>true</code>の場合のみですが、「(2)ボーイッシュリストに入れる」は<code>girl.IsBoyish</code>が<code>false</code>でも実行されてしまいます。</p> <p>ボーイッシュリストにボーイッシュじゃない女の子が入っていると都合が悪いですよね?ね?</p> <p>なので、正しくは</p> <pre><code class="csharp">if (girl.IsBoyish) { Console.Write("かわいいね"); boyishGirls.Add(girl); } </code></pre> <p>と書かなければなりません。</p> <p>※実際には<code>if</code>の<code>{}</code>を省略して複数行書くと、<code>boyishGirls.Add(girl);</code>の行のインデントはIDEなどでは効かないので、<code>if</code>の範囲外ということはすぐに分かると思います。</p> <p>こういう事情があるので、<code>if</code>と<code>else</code>が連続するような判定では<code>{}</code>を省略すると意図しない結果になる場合があります。たとえば次のようなケースをプログラムで表現するとします。</p> <pre><code class="csharp">if (!girl.HasShortHair) { if (girl.IsBokuGirl) { Console.Write("かわいいね"); } } else { Console.Write("かわいいね"); } </code></pre> <p>上記は「女の子がショートヘアでなく、かつボクっ娘である」または「女の子がショートヘアである」場合にコンソール出力をするというコードです。</p> <p>※この条件判定は私の趣味ですが「ショートヘアでもボクっ娘でもない女の子は可愛くねぇ!」という主張ではありませんのでご承知おきください。</p> <p>これを、<code>{}</code>を省略しようと思って下記のように書くとします。</p> <pre><code class="csharp">if (!girl.HasShortHair) if (girl.IsBokuGirl) Console.Write("かわいいね"); else Console.Write("かわいいね"); </code></pre> <p>一見<code>{}</code>を省けたように見えますが、前述の通りC#ではインデントはブロックとして解釈されません。つまり、上記のコードは下記のように解釈されます。</p> <pre><code class="csharp">if (!girl.HasShortHair) { if (girl.IsBokuGirl) { Console.Write("かわいいね"); } else { Console.Write("かわいいね"); } } </code></pre> <p>するとどうでしょう、「女の子がショートヘアでない場合、ボクっ娘であればコンソール出力、ボクっ娘でなくてもコンソール出力」「女の子がショートヘアの場合は何もしない」というコードになってしまいました。意図が真逆になっています。</p> <p>なので、こういうケースでは<code>{}</code>を使って<code>else</code>がどの<code>if</code>に対応しているものなのかを明示的に記載する必要があります。</p> <pre><code class="csharp">if (!girl.HasShortHair) { if (girl.IsBokuGirl) Console.Write("かわいいね"); } else Console.Write("かわいいね"); </code></pre> <p>上記のようにすれば問題なく、本来の要件を満たす条件判定となります。個人的な好みとしては<code>if</code>に<code>{}</code>を付けるなら<code>else</code>にも<code>{}</code>を(単一処理でも)付けたいです。</p> <p><code>if</code>の<code>{}</code>を省略する場合は、シンプルな処理であればタイプ量も減りシンプルで見通しの良いコードになりますが、複数条件が入り組んだ式の場合は注意が必要です。</p> <p>※説明のために意図的に分かりにくく書きましたが、上記の例であれば</p> <pre><code class="csharp">if (girl.HasShortHair || girl.IsBokuGirl) Console.Write("かわいいね"); </code></pre> <p>のようにロジックを見直して吸収できたりもするので、柔軟に考えてみると良いと思います。</p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>C#の歴史的背景に詳しくないのですが、「もともと<code>if</code>はそれに続く単一の処理をするのであり、<code>{}</code>で複数の処理を単一の処理っぽくしているのであって、省略とかではなく本来<code>{}</code>がないのが成り立ち的には正しいのである」とか…。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> あぱしょに