tag:crieit.net,2005:https://crieit.net/tags/C%23/feed 「C#」の記事 - Crieit Crieitでタグ「C#」に投稿された最近の記事 2022-06-15T07:10:44+09:00 https://crieit.net/tags/C%23/feed tag:crieit.net,2005:PublicArticle/18224 2022-06-13T18:26:17+09:00 2022-06-15T07:10:44+09:00 https://crieit.net/posts/expression-bodied-method-void 【C#】戻り値がvoidなメソッドで、式形式メンバー+三項演算子を使う <p>できるのかな?と思って興味本位でなんやかんややってみたのでメモです。</p> <p>次のようなメソッドがあるとします。</p> <pre><code class="csharp">void DoSomething() { // 何らかの処理 } void DoSomethingIfNeeded(bool isNeeded) { if (isNeeded) DoSomething(); } </code></pre> <p>この時、<code>DoSomethingIfNeeded</code> をシンプルに式形式メンバーで書きたいと思いました。イメージとしてはこんな感じ。</p> <pre><code class="csharp">void DoSomethingIfNeeded(bool ifNeeded) => isNeeded ? DoSomething() : null; </code></pre> <p>上記コードはコンパイルが通りません。<code>void</code> なのに <code>null</code> を返しているからです。</p> <p>だったら処理のない <code>DoNothing</code> 的なメソッドを実行させれば良いのでは?と思い、次のように書いてみました。</p> <pre><code class="csharp">void DoNothing() { return; } void DoSomethingIfNeeded(bool isNeeded) { isNeeded ? DoSomething() : DoNothing(); } </code></pre> <p>しかしこれもコンパイルエラーになります。</p> <blockquote> <p>CS0201: 代入、呼び出し、インクリメント、デクリメント、新しいオブジェクトの式のみがステートメントとして使用できます</p> </blockquote> <p>なるほど…。それなら三項演算子には、どのメソッドを呼び出すかの判定だけをしてもらえばいいのでは?</p> <p>ということで下記のように書いてみました。</p> <pre><code class="csharp">void DoSomethingIfNeeded(bool isNeeded) => ( isNeeded ? DoSomething : DoNothing )(); </code></pre> <p>これなら、やっていることは <code>DoSomething</code> か <code>DoNothing</code> かだから通るでしょう、という魂胆です。しかしこれも通りません。</p> <blockquote> <p>CS0149: メソッド名が必要です</p> </blockquote> <p>そうですか…。</p> <p>いやいやおかしいでしょ、実行すべきメソッドを決定しているのに、メソッド名が必要なのは変では?と思い調べてみたところ、以下の記事にたどり着きました。</p> <p>僕はね、voidが戻りの関数を三項演算子みたいに使いたかっただけなんだ<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/vranometria/items/cfe6f282d7973ae7f52b">https://qiita.com/vranometria/items/cfe6f282d7973ae7f52b</a></p> <p>キャスト…。どちらも <code>Action</code> であることは明らかだと思うのだけど、明示的なキャストが必要なのか…。何故…。</p> <p>キャストを入れてみます。</p> <pre><code class="csharp">void DoSomethingIfNeeded(bool isNeeded) => ( isNeeded ? (Action)DoSomething : DoNothing )(); </code></pre> <p>これでコンパイルが通りました。</p> <p>あとは、何もしない処理にわざわざ <code>DoNothing</code> という名前を付ける必要もないので匿名化します。</p> <pre><code class="csharp">void DoSomethingIfNeeded(bool isNeeded) => ( isNeeded ? (Action)DoSomething : () => { } )(); </code></pre> <p>これで一応、当初の目的は達成できました。<code>DoSomething</code> に簡単な処理を入れて実行してみます。</p> <pre><code class="csharp">void DoSomething() { Console.WriteLine("hoge"); Console.WriteLine("fuga"); } void DoSomethingIfNeeded(bool isNeeded) => ( isNeeded ? (Action)DoSomething : () => { } )(); DoSomethingIfNeeded(true); Console.WriteLine("piyo"); // hoge // fuga // piyo DoSomethingIfNeeded(false); Console.WriteLine("piyo") // piyo </code></pre> <p>できました。キャストが必要な理由は勉強が必要。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <p>僕はね、voidが戻りの関数を三項演算子みたいに使いたかっただけなんだ<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/vranometria/items/cfe6f282d7973ae7f52b">https://qiita.com/vranometria/items/cfe6f282d7973ae7f52b</a></p> <p>Expression-bodied members (C# programming guide)<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members">https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members</a></p> あぱしょに tag:crieit.net,2005:PublicArticle/18209 2022-06-06T18:50:19+09:00 2022-06-06T18:50:19+09:00 https://crieit.net/posts/wrap-linq-methods 【C#】LINQ のメソッド名が SQL っぽいからラップする <h2 id="SQL チックなメソッド名"><a href="#SQL+%E3%83%81%E3%83%83%E3%82%AF%E3%81%AA%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E5%90%8D">SQL チックなメソッド名</a></h2> <p>C# の <em>LINQ to Object</em>(以下、単に LINQ と記載) はとても便利なんですけど、メソッド名が SQL っぽくて、いまいち直感的ではない上にダサいです。例として、JavaScript と比較してみましょう。<br /> <div class="table-responsive"><table> <thead> <tr> <th>LINQ でのメソッド名</th> <th>JavaScript のメソッド名</th> </tr> </thead> <tbody> <tr> <td>Select</td> <td>map</td> </tr> <tr> <td>Where</td> <td>filter</td> </tr> <tr> <td>OrderBy</td> <td>sort</td> </tr> </tbody> </table></div></p> <p>以下は、実際に利用する例です。<br /> JavaScript ではこう。</p> <pre><code class="javascript">const hoge = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; const fuga = hoge.filter(x => x % 2 === 0).map(x => x * 2).sort(); </code></pre> <p>C# ではこう。</p> <pre><code class="csharp">var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; var fuga = hoge.Where(x => x % 2 == 0).Select(x => x * 2).OrderBy(x => x).ToArray(); </code></pre> <p>もちろん慣れてくれば <code>Select</code> だろうが <code>Where</code> だろうがスラスラ読めるのですが、慣れていなければ引っかかるかもしれません。一方、<code>map</code>, <code>filter</code>, <code>sort</code> というのは直感的で意味がわかりやすいです。</p> <h2 id="ラップする"><a href="#%E3%83%A9%E3%83%83%E3%83%97%E3%81%99%E3%82%8B">ラップする</a></h2> <p>ということで、ラッパーメソッドで名前を変えます。</p> <h3><code>Select</code> => <code>Map</code></h3> <pre><code class="csharp">public static IEnumerable<TResult> Map<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func) => source.Select(x => func(x)); public static IEnumerable<TResult> Map<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> func) => source.Select((x, index) => func(x, index)); </code></pre> <h3><code>Where</code> => <code>Filter</code></h3> <pre><code class="csharp">public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<bool> func) => source.Where(x => func()); public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> func) => source.Where(x => func(x)); public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, bool func) => source.Where(x => func); </code></pre> <h3><code>OrderBy</code> => <code>Sort</code></h3> <pre><code class="csharp">public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) => sources.OrderBy(keySelector, comparer); public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector) => sources.OrderBy(keySelector); public static IOrderedEnumerable<TSource> SortByDesc<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) => sources.OrderByDescending(keySelector, comparer); public static IOrderedEnumerable<TSource> SortByDesc<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector) => sources.OrderByDescending(keySelector); </code></pre> <p>ソートに関しては、元の <code>OrderBy</code> が、昇順か降順かでメソッドが別れています。昇順のソートが <code>OrderBy</code>、降順のソートが <code>OrderByDescending</code> なので、それぞれ <code>Sort</code>、 <code>SortByDesc</code> というメソッド名でラップしましたが、ここは昇順か降順かをオプション引数で切り替える <code>Sort</code> という名前のメソッドにしても良いでしょう。</p> <p>「昇順か降順かをメソッド名だけで判断できる」という利点はありますが、同じようなことが別の名前のメソッドで定義されているのも、個人的には気持ち悪いと感じます。</p> <p>同様に、<code>ThenBy</code> と <code>ThenByDescending</code> も <code>Then</code> というメソッドでラップしてみます。</p> <pre><code class="csharp">public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool byDesc) => byDesc ? sources.OrderByDescending(keySelector, comparer) : sources.OrderBy(keySelector, comparer); public static IOrderedEnumerable<TSource> Sort<TSource, TKey>(this IEnumerable<TSource> sources, Func<TSource, TKey> keySelector, bool byDesc) => byDesc ? sources.OrderByDescending(keySelector) : sources.OrderBy(keySelector); public static IOrderedEnumerable<TSource> Then<TSource, TKey>(this IOrderedEnumerable<TSource> sources, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool byDesc) => byDesc ? sources.ThenByDescending(keySelector, comparer) : sources.ThenBy(keySelector, comparer); public static IOrderedEnumerable<TSource> Then<TSource, TKey>(this IOrderedEnumerable<TSource> sources, Func<TSource, TKey> keySelector, bool byDesc) => byDesc ? sources.ThenByDescending(keySelector) : sources.ThenBy(keySelector); </code></pre> <p>オプション引数 <code>byDesc</code> を設定しました。これにより、次のようにコードを書けます。</p> <pre><code class="csharp">// ラップ前 var hoge = GetEmployee.OrderBy(s => s.Age).ThenByDesc(x => x.Salary); // ラップ後 var hoge = GetEmployee.Sort(s => s.Age).Then(s => s.Salary, byDesc: true); </code></pre> <p>もちろん引数名の <code>byDesc</code> を省略することもできますが、理由があり記載しています。理由については、以下の記事で解説しているので、よかったらご覧ください。</p> <p><a href="https://crieit.net/posts/dare-to-write-argument-names-patterns">【C#】あえて引数名を書くパターン</a></p> <h2 id="比較"><a href="#%E6%AF%94%E8%BC%83">比較</a></h2> <p>メソッド名をラップしてみることで、冒頭のコードは次のように書けるようになりました。</p> <pre><code class="csharp">// 変更前 var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; var fuga = hoge.Where(x => x % 2 == 0).Select(x => x * 2).OrderBy(x => x).ToArray(); // 変更後 var hoge = new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; var fuga = hoge.Filter(x => x % 2 == 0).Map(x => x * 2).Sort(x => x).ToArray(); </code></pre> <p>LINQ にあまり慣れていない人でも、よりスッと入ってきやすいコードになったのではないでしょうか。</p> あぱしょに tag:crieit.net,2005:PublicArticle/18208 2022-06-02T19:00:09+09:00 2022-06-15T09:04:01+09:00 https://crieit.net/posts/dare-to-write-argument-names-patterns 【C#】あえて引数名を書くパターン <p>C# では、メソッドやコンストラクタに引数を渡す際に引数名を省略してコードを書く場合が多いと思いますが、明示的に引数名を書くこともできます。</p> <pre><code class="csharp">int Add(int x, int y) => x + y; // 引数名を省略する場合 var hoge = Add(10, 20); // 引数名を明記する場合 var fuga = Add(x: 10, y: 20); </code></pre> <p>省略しても問題ない引数名を、わざわざ明記するパターンは以下のような場合が考えられます。</p> <h2 id="メソッドで定義している引数の順番とは違う順番で書ける"><a href="#%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A7%E5%AE%9A%E7%BE%A9%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E5%BC%95%E6%95%B0%E3%81%AE%E9%A0%86%E7%95%AA%E3%81%A8%E3%81%AF%E9%81%95%E3%81%86%E9%A0%86%E7%95%AA%E3%81%A7%E6%9B%B8%E3%81%91%E3%82%8B">メソッドで定義している引数の順番とは違う順番で書ける</a></h2> <pre><code class="csharp">float Div(float x, float y) => x / y; // x と y をメソッド定義とは異なる順番で渡す Console.Write(Div(y: 2f, x: 1f)); // 0.5 </code></pre> <p>とはいえ、何か特別な意図がない限りこういった使い方はしないと思います。</p> <h2 id="複数の省略可能引数(オプション引数)のうち一つを明示的に指定する"><a href="#%E8%A4%87%E6%95%B0%E3%81%AE%E7%9C%81%E7%95%A5%E5%8F%AF%E8%83%BD%E5%BC%95%E6%95%B0%EF%BC%88%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E5%BC%95%E6%95%B0%EF%BC%89%E3%81%AE%E3%81%86%E3%81%A1%E4%B8%80%E3%81%A4%E3%82%92%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AB%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">複数の省略可能引数(オプション引数)のうち一つを明示的に指定する</a></h2> <pre><code class="csharp">void Demo(int x, bool isHoge = false, bool isFuga = false, bool isPiyo = false) { Console.WriteLine($"x: {x}"); Console.WriteLine($"isHoge: {isHoge}"); Console.WriteLine($"isFuga: {isFuga}"); Console.WriteLine($"isPiyo: {isPiyo}"); } // isHoge と isFuga はデフォルト値のまま、isPiyo のみ指定 Demo(10, isPiyo: true); // x: 10 // isHoge: False // isFuga: False // isPiyo: True // 名前付き引数を使用しない場合、すべての引数を明示的に指定する必要がある Demo(10, false, false, true); </code></pre> <p>これも、元々のメソッドの設計があまり良くないなどの理由で、後付けで省略可能引数がわんさか付いているようなメソッドというのは古いコードではよくあります。</p> <h2 id="可読性の向上;「この引数でどうなるんだっけ?」対策"><a href="#%E5%8F%AF%E8%AA%AD%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A%EF%BC%9B%E3%80%8C%E3%81%93%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%A7%E3%81%A9%E3%81%86%E3%81%AA%E3%82%8B%E3%82%93%E3%81%A0%E3%81%A3%E3%81%91%EF%BC%9F%E3%80%8D%E5%AF%BE%E7%AD%96">可読性の向上;「この引数でどうなるんだっけ?」対策</a></h2> <p>例として以下の <code>StreamWriter</code> の場合を考えてみます。</p> <pre><code class="csharp">var path = GetPath(); using (var sw = new StreamWriter(path, flase)) sw.Write("hoge"); </code></pre> <p><code>StreamWriter</code> のコンストラクタの第2引数に <code>false</code> を渡しています。この第2引数は <code>append</code> という引数名で定義されており、既にファイルが存在する場合に末尾に書き込むか、新しく上書きするかを真偽値で指定します<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p> <p>ところが数年ぶりに <code>StreamWriter</code> を使ってこのコードを見た場合、この <code>false</code> がどういう作用をもたらすのか<strong>瞬時に</strong>理解できるでしょうか?</p> <p>もちろん、IDE上で見る場合は引数の説明をいつでも参照することができます。そうでない場合でも、<a target="_blank" rel="nofollow noopener" href="https://referencesource.microsoft.com/">公式のリファレンスソース</a>を参照することができることでしょう。この <code>false</code> が何をもたらすかというのは、数十秒あれば解決できる疑問かもしれません。</p> <p>しかし、引数名を明記しておくと数十秒とかからず、<strong>コードを見ただけで瞬時に</strong>判断できるようになります。</p> <pre><code class="csharp">var path = GetPath(); using (var sw = new StreamWriter(path, append: flase)) sw.Write("hoge"); </code></pre> <p>「この <code>false</code> の引数名、<code>append</code> ってことはこの場合追記じゃなくて上書きなのか」と、<strong>定義やドキュメントを参照するまでもなく</strong>コードを読んだ瞬間に意味を理解することができます。これはかなり大きなメリットなのではないでしょうか。</p> <p>さらに、今回は <code>StreamWriter</code> を例に出しましたが、もしこれが公式のライブラリではなく、ドキュメントもろくにメンテされていない独自ライブラリだとしたらどうでしょう。「そのような環境の方が問題だろ!」というのももっともですが、しかし全てのドキュメントのメンテが行き届いている環境ばかりではありません。</p> <p>個人的には、コメントに「falseは上書き」みたいなことを書くよりは、<code>append: false</code> と書いた方が余程良いと思います<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p> <hr /> <p>以上、個人的に想定する「引数名を明示する理由」をまとめてみました。</p> <p>以下、おまけ。</p> <p>最後のコメント的な用法に関しては、自作クラスを使う場合は <code>WritingMode</code> みたいな <code>Enum</code> を受け取るようにして、</p> <pre><code class="csharp">using (var ow = OriginalWriter(path, WritingMode.Overwrite)) ow.Write("hoge"); // WritingMode は // WritingMode.Overwrite => 上書き // WritingMode.Append => 追記 </code></pre> <p>という形で受け取るようにすれば、引数名を書かずとも「<strong>この引数では書込みモードを指定してますよ</strong>」という意図が明確になることもあります。自作する場合は、たとえ真偽値で判断できる場合でもあえて <code>Enum</code> を併用して、より意図を込めやすくなるように工夫できれば良いと思います。</p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p><strong>参考</strong> StreamWriter クラス|StreamWriter(String, Boolean)<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/api/system.io.streamwriter?view=net-6.0">https://docs.microsoft.com/ja-jp/dotnet/api/system.io.streamwriter?view=net-6.0</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:2" role="doc-endnote"> <p>コメントが悪という主張ではなく、<strong>コードで表現できることは、できるだけコードで表現すべき</strong>という主張です。たとえば「なぜ追記じゃなくて上書きをするのか」という意図が共有してあるのは良いコメントだと感じます。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> あぱしょに 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/17761 2021-11-12T01:52:27+09:00 2021-11-12T02:06:23+09:00 https://crieit.net/posts/grpc-cs-dotnet6-on-mac C#(.Net 6)でgRPCサーバー on Mac <p>.Net 6 も出たし……と思い、C# で gRPC のサンプルをMacで動かそうとしたらエラーで落ちてちょっと戸惑ったので、手順を書いておきます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio-code">ASP.NET Core で .NET Core gRPC のクライアントとサーバーを作成する | Microsoft Docs</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/grpc/test-tools?view=aspnetcore-6.0">ASP.NET Core で gRPCurl を使用して gRPC サービスをテストする | Microsoft Docs</a></li> </ul> <h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1> <h2 id="テンプレートからプロジェクトを作る"><a href="#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E4%BD%9C%E3%82%8B">テンプレートからプロジェクトを作る</a></h2> <p>gRPCのテンプレからプロジェクトを作ります。</p> <pre><code class="bash">$ dotnet new grpc -o GrpcGreeter </code></pre> <h2 id="実行(落ちる)"><a href="#%E5%AE%9F%E8%A1%8C%EF%BC%88%E8%90%BD%E3%81%A1%E3%82%8B%EF%BC%89">実行(落ちる)</a></h2> <p>普通に実行します。するとMacでは落ちます。</p> <pre><code class="bash">$ cd GrpcGreeter $ dotnet run ビルドしています... Unhandled exception. System.IO.IOException: Failed to bind to address https://localhost:7229. ---> System.AggregateException: One or more errors occurred. (HTTP/2 over TLS is not supported on macOS due to missing ALPN support.) (HTTP/2 over TLS is not supported on macOS due to missing ALPN support.) </code></pre> <h2 id="対処:TLSを使わない"><a href="#%E5%AF%BE%E5%87%A6%EF%BC%9ATLS%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84">対処:TLSを使わない</a></h2> <p>落ちる理由はエラーメッセージにも <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio-code">チュートリアル</a> にも書いてあって、Macや古いWindowsではHTTP2 on TLSがサポートされていないからです。よってTLSを使わないようにすれば解決です。</p> <p>で、<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos">.NET Core での gRPC のトラブルシューティング | Microsoft Docs</a>にはProgram.csに書き足せという旨が書かれているわけですが、これが正直よくわからない。たぶんテンプレートの形式が変わってしまったのだろうなと思われます。</p> <p>というわけで、次のようにして雑に解決しました。</p> <p>Properties/launchSettings.json には</p> <pre><code class="json">{ "profiles": { "GrpcGreeter": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "applicationUrl": "http://localhost:5142;https://localhost:7229", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } </code></pre> <p>とありますが、これの <code>applicationUrl</code> からhttpsのほうのURLを削り、</p> <pre><code class="json">{ "profiles": { "GrpcGreeter": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "applicationUrl": "http://localhost:5142", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } </code></pre> <p>として保存します。</p> <h2 id="実行"><a href="#%E5%AE%9F%E8%A1%8C">実行</a></h2> <p>これで実行できます。</p> <pre><code class="bash">$ dotnet run ビルドしています... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5142 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: /Users/suzusime/sandbox/GrpcGreeter/ </code></pre> <h2 id="grpcurlでテスト"><a href="#grpcurl%E3%81%A7%E3%83%86%E3%82%B9%E3%83%88">grpcurlでテスト</a></h2> <p>これでサーバーが立つことがわかったので、grpcurlでAPIを叩いてみます。</p> <h3 id="reflectionを追加"><a href="#reflection%E3%82%92%E8%BF%BD%E5%8A%A0">reflectionを追加</a></h3> <p>grpcurlで叩くにはサーバーにreflection機能を追加する必要があるので、追加します(Goでやったときは不要だった気がするので本当は不要なのかも)。</p> <p>まずは</p> <pre><code class="bash">$ dotnet add package Grpc.AspNetCore.Server.Reflection --version 2.40.0 </code></pre> <p>でプロジェクトを追加します。</p> <p>次に、<code>Program.cs</code>を</p> <pre><code class="cs">using GrpcGreeter.Services; var builder = WebApplication.CreateBuilder(args); // Additional configuration is required to successfully run gRPC on macOS. // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 // Add services to the container. builder.Services.AddGrpc(); builder.Services.AddGrpcReflection(); // 追加 var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGrpcService<GreeterService>(); app.MapGrpcReflectionService(); // 追加 app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); app.Run(); </code></pre> <p>のように編集します。</p> <h3 id="grpcurlのインストール"><a href="#grpcurl%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">grpcurlのインストール</a></h3> <p>homebrewで入ります。</p> <pre><code class="bash">$ brew install grpcurl </code></pre> <h3 id="grpcurlで叩く"><a href="#grpcurl%E3%81%A7%E5%8F%A9%E3%81%8F">grpcurlで叩く</a></h3> <p>サーバーを立てます。</p> <pre><code class="bash">$ dotnet run </code></pre> <p>別のコンソールを開いてgrpcurlでサーバーを叩きましょう。まずは describe でAPIの使い方を調べてみます。</p> <pre><code class="bash"># 機能の一覧を出す $ grpcurl -plaintext localhost:5142 describe greet.Greeter is a service: service Greeter { rpc SayHello ( .greet.HelloRequest ) returns ( .greet.HelloReply ); } grpc.reflection.v1alpha.ServerReflection is a service: service ServerReflection { rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse ); } # 型の詳細を見る $ grpcurl -plaintext localhost:5142 describe greet.HelloRequest greet.HelloRequest is a message: message HelloRequest { string name = 1; } </code></pre> <p>これでAPIの叩き方がわかったので、リクエストを送ってみます。</p> <pre><code class="bash">$ grpcurl -plaintext -d '{ "name": "世界" }' localhost:5142 greet.Greeter/SayHello { "message": "Hello 世界" } </code></pre> <p>gRPCのサーバーが動いていることが確かめられました。</p> <h1 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h1> <ul> <li>protobufのパスは GrpcGreeter.csproj で指定できるようになっています。protobufを変更しても、dotnet runしたときにいいかんじにC#のファイルも生成してくれるようです。</li> <li>サーバーの実装は Services/GreeterService.cs にあります(見れば分かりますが)。Greeter.GreeterBaseという自動生成される基底クラスを継承してオーバーライドしていく方式みたいです。</li> </ul> <h1 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h1> <p>テンプレに従うだけで動かせるので、とりあえずgRPCを試してみたいという需要ならGoでやる以上に楽ですね。</p> <p>あと、Visual Studio for Mac 2022 Previewも入れてみたのですが、重くて正直微妙でした。VS Codeの出来が良すぎる……。</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> あぱしょに tag:crieit.net,2005:PublicArticle/17493 2021-07-08T00:31:43+09:00 2021-11-17T19:10:52+09:00 https://crieit.net/posts/C-60e5c8dfb4edf C#でゲームを作った話 <h4 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h4> <p>C#でゲームを作りました。</p> <p><a href="https://crieit.now.sh/upload_images/527fca1d8be3c6f572d48ecc3b3eaa2460e5af73606e2.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/527fca1d8be3c6f572d48ecc3b3eaa2460e5af73606e2.jpg?mw=700" alt="image" /></a></p> <p>ゲームの詳細やダウンロードは以下からどうぞ。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.freem.ne.jp/win/game/25930">ダウンロード</a></p> <p>今回はC#でゲーム作ろうとした経緯から完成して配布までに感じた事を書いてみます。C#でというか、ゲーム作成ツールを使わずにゼロからまともにゲームを作ったのは初めてですので、その視点からの学びも記録します。</p> <h4 id="発端"><a href="#%E7%99%BA%E7%AB%AF">発端</a></h4> <p>自分は仕事でSEをやっていますが、去年、緊急事態宣言が始まったタイミングでその時の出向先での業務が<strong>次の現場が決まらないまま</strong>終わりました。<br /> 今は普通に仕事はありますが、あの頃は一時的にSEの仕事も減った印象があります。<br /> で、仕方ないので3か月間ほど、Teamsを使って同じく仕事がなくなった自社の若いメンバーにC言語、C++と、(自分もほとんど触ったことのない)C#を何故か教えていました。それがきっかけでC#を勉強し、勉強しつつ何か作ろうってことで今回のゲームを作り始めました。</p> <h4 id="なぜコンソールアプリ・・・"><a href="#%E3%81%AA%E3%81%9C%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%83%BB%E3%83%BB%E3%83%BB">なぜコンソールアプリ・・・</a></h4> <p>UnityとかGUIなやつだと、C#での実装以外の作業が多くなるかなと思ったのと、どんどん盛りだくさんになっていって絶対最後まで完成しないと思ったからです。<br /> 多分その予想は正しかったと思います。</p> <h4 id="セーブデータはどうする"><a href="#%E3%82%BB%E3%83%BC%E3%83%96%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E3%81%A9%E3%81%86%E3%81%99%E3%82%8B">セーブデータはどうする</a></h4> <p>作る前からゲームのセーブデータをどうしようって思っていました。<br /> いざ作る時になって腹を決めて調べたら、.NETにはオブジェクトの内容をXMLの形で書き出し・読み出しができるクソ便利なクラスがあったのでそれを使う事にしました。詳しくは「.NET XML シリアライズ」で検索してください。<br /> 実際にはセーブデータとして書き込み・読み出しされるSaveDataクラスを用意して、その中にセーブデータとして残したいメンバをぶちこみます。でSaveDataのインスタンスをXMLシリアライズ・デシリアライズしてsave/loadを実現しました。もちろんそのままだと簡単に編集できてしまうので、XMLデータの暗号化・複合化も行っています。</p> <p>この時注意したいのが、「SaveDataにreadonlyのデータを含めない」事です。セーブデータの容量が無駄に増えるだけの話ではありません。ゲームのアップデートでそのreadonlyのデータに変更が入っても、アプデ前のセーブデータを使うと当然ながらアプデ前のreadonlyデータになってしまい、変更が反映されなくなってしまう可能性があるからです(セーブデータに互換性がなくなる)。</p> <p>「readonlyのデータをセーブデータに含めるわけねーだろ」って確かにその通りなのですが、最初にしっかり設計できていればの話で、最終的に1つのstructの中にreadonlyとそうでないデータが混在している・・・ってあるんじゃないでしょうか? 無い?</p> <h4 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h4> <p>今回は、完成後のテストを十分に行いました。これの前に作った<a target="_blank" rel="nofollow noopener" href="https://www.freem.ne.jp/win/game/6005">ゲーム</a>があるのですが、そっちはあまりテストせずに公開し、延々とバグ報告があがる恐怖を味わいました。<br /> 何故ろくにテストせずに公開したのかというと、完成間近の時は、早く遊んでもらいたい、反応を見たいという欲がすごくなってしまったからです。今回もその誘惑がすごかったのですが、なんとか耐えてテストしきりました。<br /> 前に作った方はジャンル効果で幸運にも沢山遊んでいただき、フィードバックも十分にあったので結果オーライだったのですが、もしあまり遊ばれなかったらバグもそのままで、あとになって自分でプレイしたときに「こんなバグを誰からも指摘されず自分で発見してしまうなんて・・・全然遊ばれてなかったのか・・・・」と立ち直れなくなると思います。</p> <h4 id=".NETアプリの難読化"><a href="#.NET%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E9%9B%A3%E8%AA%AD%E5%8C%96">.NETアプリの難読化</a></h4> <p>.NETで作ったアプリは、ほぼそのままのソースでリバースエンジニアリングできてしまうという特徴があります。なので<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/visualstudio/ide/dotfuscator/?view=vs-2019">MicroSoftも難読化を推奨</a>しているのですが、やる気になって色々難読化ツールを調べて使ってみても、以下の問題がありました。</p> <p>・難読化したら動かなくなった<br /> ・難読化するとウイルス判定されやすくなった<br /> ・使えそうな難読化ツールがみんな有料(しかも高価)</p> <p>どうしろと?みたいな状況です。<br /> 仕方ないので、VisualStudio経由でインストールできるDotfuscatorのCE版を使用しています。<br /> ただしこれは、かなり甘い難読化です。文字列リテラルとかそのままです。<br /> DotfuscatorのCE版より高機能で、無料でできる、ウイルス判定されない難読化ツールあったら教えてください。(切実)</p> <h4 id="どこで配布するか"><a href="#%E3%81%A9%E3%81%93%E3%81%A7%E9%85%8D%E5%B8%83%E3%81%99%E3%82%8B%E3%81%8B">どこで配布するか</a></h4> <p>今回はファイルそのものは<a target="_blank" rel="nofollow noopener" href="https://www.freem.ne.jp/">ふりーむ</a>さん一本にしました。<br /> 配布元は↑一本ですが、<a target="_blank" rel="nofollow noopener" href="https://freegame-mugen.jp/">フリーゲーム夢現</a>さんにも登録して入り口を増やしています。<br /> 前回は<a target="_blank" rel="nofollow noopener" href="https://booth.pm/ja">BOOTH</a>、<a target="_blank" rel="nofollow noopener" href="https://www.vector.co.jp/">Vector</a>にも置きましたが、ゲームの場合は感想やコメントをもらいやすく、アプデも検知されやすい(かもしれない)ふりーむと夢現に絞った方がよいのではないかと思いました。<br /> ちなみに、適当なアップローダーに置いて配布するのはやめましょう。ダウンロード先が信用できないとして、要注意アプリ扱いされる場合があります。</p> <h4 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h4> <p>今回のゲームが完成間近のタイミングで、任天堂から「<a target="_blank" rel="nofollow noopener" href="https://www.nintendo.co.jp/switch/awuxa/">ナビつき!つくってわかるはじめてゲームプログラミング</a>」がリリースされました。購入しましたが、今回のゲームが完成し、燃え尽きてなにもゲームのアイデアが無く、全然何も作れてない状態です。チュートリアルは全部クリアしましたが、正直このまま積みそうです。<br /> ゲームのアイデアは尽きてますが、せっかくC#を勉強したので、Unityもやってみたいですね。勉強だけして満足しそうな気がしますが。</p> uskz tag:crieit.net,2005:PublicArticle/17072 2021-05-08T22:36:30+09:00 2021-05-08T22:36:30+09:00 https://crieit.net/posts/switch-expression-in-csharp-8 C# 8.0のswitch式 <h1 id="switch式"><a href="#switch%E5%BC%8F">switch式</a></h1> <p>C#の<code>switch</code>が、C# 8.0から式で使えるようになってます。いいですね。</p> <p>良い例が思い浮かばないのが申し訳ないですが、このような<code>Enum</code>があるとします。</p> <pre><code class="csharp">public enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } </code></pre> <p>ある日付の曜日を日本語で表示するための<code>switch</code>を使用するとします。従来はこのように書いていました。</p> <pre><code class="csharp">var dow_eng = DateTime.Today.DayOfWeek; var dow_jpn = ""; switch ((DayOfWeek)dow_eng) { case DayOfWeek.Sunday: dow_jpn = "日曜日"; break; case DayOfWeek.Monday: dow_jpn = "月曜日"; break; case DayOfWeek.Tuesday: dow_jpn = "火曜日"; break; case DayOfWeek.Wednesday: dow_jpn = "水曜日"; break; case DayOfWeek.Thursday: dow_jpn = "木曜日"; break; case DayOfWeek.Friday: dow_jpn = "金曜日"; break; case DayOfWeek.Saturday: dow_jpn = "土曜日"; break; default: dow_jpn = "エラー"; break; } Console.Write(dow_jpn); </code></pre> <p>なっが~~。これいちいち<code>case</code>とか<code>default</code>とか<code>break</code>とかだらだらと書かなきゃいけなくて、すごく面倒ですよね。</p> <p>これがC# 8.0ではこう書けます。</p> <pre><code class="csharp">var dow_eng = DateTime.Today.DayOfWeek; var dow_jpn = (DayOfWeek)dow_eng switch { DayOfWeek.Sunday => "日曜日", DayOfWeek.Monday => "月曜日", DayOfWeek.Tuesday => "火曜日", DayOfWeek.Wednesday => "水曜日", DayOfWeek.Thursday => "木曜日", DayOfWeek.Friday => "金曜日", DayOfWeek.Saturday => "土曜日", _ => "エラー", }; Console.Write(dow_jpn); </code></pre> <p>すっきりしましたね。特徴としては<code>switch式</code>なので、それ自体が文字列を返してくれます。長ったらしく<code>case</code>とかを書かずに済むのはとても良いですね。</p> <p><code>default</code>に当たる処理は<code>_</code>とマッチさせます。</p> <h2><code>_</code>が使われている場合は無視される</h2> <p>C#は<code>string</code>も<code>switch</code>に使用できます。その場合、<code>case</code>には変数(定数)も使用できます。</p> <p>では<code>_</code>が既に変数(定数)として使用されている場合はどうなるでしょうか。</p> <pre><code class="csharp">var lastName = "八千崎"; const string _ = "雪村"; const string KuraueHinata_last = "倉上"; const string AobaKokona_last = "青羽"; const string SaitoKaede_last = "斎藤"; const string KurosakiHonoka_last = "黒崎"; var firstName = lastName switch { KuraueHinata_last => "ひなた", AobaKokona_last => "ここな", SaitoKaede_last => "かえで", KurosakiHonoka_last => "ほのか", _ => "あおい", }; Console.Write(firstName); // 出力結果:あおい </code></pre> <p>なんと、定数としての<code>_</code>とはマッチせずに、<code>default</code>のキーワードとしての<code>_</code>としてマッチしてしまいました。この結果、八千崎という人があおいちゃんと認識されてしまいました。これはまずい!</p> <p>まぁまずないとは思いますが、<code>_</code>という変数(定数)はうまくマッチングできないので使用を控えましょう。個人的には<code>out</code>引数でも特別な意味のあるキーワードなので<code>_</code>という変数などは宣言できないようにしてほしいのですが、まぁ後方互換性とかもあるのでね、仕方ないですね…。</p> <p>あおいちゃんとマッチングさせたくない場合は、面倒くさがらず</p> <pre><code class="csharp">var lastName = "八千崎"; const string AoiYukimura_last = "雪村"; const string KuraueHinata_last = "倉上"; const string AobaKokona_last = "青羽"; const string SaitoKaede_last = "斎藤"; const string KurosakiHonoka_last = "黒崎"; var firstName = lastName switch { AoiYukimura_last => "あおい", KuraueHinata_last => "ひなた", AobaKokona_last => "ここな", SaitoKaede_last => "かえで", KurosakiHonoka_last => "ほのか", _ => "そんな子おらん", }; Console.Write(firstName); // 出力結果:そんな子おらん </code></pre> <p>としましょう。</p> <h1>複数<code>case</code>とのマッチは不可</h1> <p>ただし、複数の<code>case</code>とマッチさせることはできません。</p> <p>具体的には、平日か休日かを判定したい場合、従来は</p> <pre><code class="csharp">var dow_eng = DateTime.Today.DayOfWeek; var workdayOrHoliday = ""; switch ((DayOfWeek)dow_eng) { case DayOfWeek.Monday: case DayOfWeek.Tuesday: case DayOfWeek.Wednesday: case DayOfWeek.Thursday: case DayOfWeek.Friday: workdayOrHoliday = "平日"; break; case DayOfWeek.Saturday: case DayOfWeek.Sunday: workdayOrHoliday = "休日"; break; default: workdayOrHoliday = "エラー"; break; } Console.Write(workdayOrHoliday); </code></pre> <p>この様に、月~金は平日、土・日は休日とまとめて指定できたのですが、新しい<code>switch式</code>ではそれができません。</p> <pre><code class="csharp">var dow_eng = DateTime.Today.DayOfWeek; var workdayOrHoliday = (DayOfWeek)dow_eng switch { DayOfWeek.Sunday => "休日", DayOfWeek.Monday => "平日", DayOfWeek.Tuesday => "平日", DayOfWeek.Wednesday => "平日", DayOfWeek.Thursday => "平日", DayOfWeek.Friday => "平日", DayOfWeek.Saturday => "休日", _ => "エラー", }; Console.Write(workdayOrHoliday); </code></pre> <p>と書くしかないんですね。これは嫌ですね。個人的には以下のような感じで書ければなと思っています。このコードは動きません。</p> <pre><code class="csharp">var dow_eng = DateTime.Today.DayOfWeek; var workdayOrHoliday = (DayOfWeek)dow_eng switch { DayOfWeek.Monday | DayOfWeek.Tuesday | DayOfWeek.Wednesday | DayOfWeek.Thursday | DayOfWeek.Friday => "平日", DayOfWeek.Sunday | DayOfWeek.Saturday => "休日", _ => "エラー", }; Console.Write(workdayOrHoliday); </code></pre> <p>今後拡張されるかもしれないので、楽しみに待とうと思います。</p> <p>ということでC# 8.0における<code>switch式</code>の備忘録でした。</p> <h1 id="おまけ"><a href="#%E3%81%8A%E3%81%BE%E3%81%91">おまけ</a></h1> <p>まぁ例が悪かったのでアレなんですけど、単純に日本語の曜日を取得するだけなら</p> <pre><code class="csharp">var culture_jpn = CultureInfo.GetCultureInfo("ja-JP"); var dow_jpn = DateTime.Today.ToString("dddd", culture_jpn); Console.Write(dow_jpn); </code></pre> <p>上記でできます。実行環境が日本語カルチャの設定になっているなら、カルチャの指定すら不要です。</p> <pre><code class="csharp">// 実行環境が日本カルチャの場合 var dow_jpn = DateTime.Today.ToString("dddd"); Console.Write(dow_jpn); </code></pre> あぱしょに tag:crieit.net,2005:PublicArticle/16720 2021-03-06T10:10:58+09:00 2021-03-06T10:10:58+09:00 https://crieit.net/posts/Unity-6042d6a2eaa82 Unityで複数の入力方法に対応するタイピングゲームを作ろう <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p>はじめまして、情報系の大学に通っているプログラム初心者の大学生です。</p> <p>2年前にゼミ内で小さなゲームジャムが開催され、タイピングゲームを作ることになったのですが、ネット上で紹介されている既存のタイピングゲームの制作方法では入力方法に以下のような問題があると思いました。</p> <blockquote> <p><strong>1. 単語を打つ手段があらかじめ決まっている。</strong><br /> 例 : "し"と打つときに"si"と打たなければいけなく、"shi"には対応していない。</p> <p><strong>2.単語を別々に入力できない。</strong><br /> 例 : "ちゃ"と打つときに"ち"と"ゃ"を別々にタイプできない。</p> </blockquote> <p>そこで今回はどんな打ち方にも対応できるタイピングゲームの制作に挑戦してみました。<br /> この記事では仕組みとプログラムに関して紹介していきます、参考にしていただければ幸いです。</p> <h2 id="参考にした作品"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E4%BD%9C%E5%93%81">参考にした作品</a></h2> <p>今回制作したプログラムでは「寿司打」のタイピングシステムを参考にしています。</p> <blockquote> <h5 id="寿司打"><a href="#%E5%AF%BF%E5%8F%B8%E6%89%93">寿司打</a></h5> <p><a target="_blank" rel="nofollow noopener" href="http://typingx0.net/sushida/play.html">こちらからプレイできます。</a></p> </blockquote> <p>どのようなシステムかというと</p> <blockquote> <p><a href="https://crieit.now.sh/upload_images/fa30eaea18027ff67c2127f6331493a76041ad3275209.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fa30eaea18027ff67c2127f6331493a76041ad3275209.PNG?mw=700" alt="キャプチャ1.PNG" /></a><br /> ゲーム画面ではこのようにタイプする文字と入力方法が表示されています。<br /> <strong>この状態で"し"を"si"ではなく"shi"と入力しようとすると...</strong></p> <p><a href="https://crieit.now.sh/upload_images/bebc0f0840b25025f197a22b24571bb26041ae559e71d.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bebc0f0840b25025f197a22b24571bb26041ae559e71d.PNG?mw=700" alt="キャプチャ3.PNG" /></a><br /> <strong>"sh"と入力した時点で1つ目の"し"の入力方法が"shi"に置き換わります。</strong><br /> (2つ目の"し"の入力方法は変わらず表示されている。)</p> </blockquote> <p>他にも</p> <blockquote> <p><a href="https://crieit.now.sh/upload_images/2f27d696448dc1a99860914e503c80d76041b0d905c92.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2f27d696448dc1a99860914e503c80d76041b0d905c92.PNG?mw=700" alt="キャプチャ4.PNG" /></a><br /> <strong>"ック"を入力する際に、"xtu"と"ku"で別々に入力しようとすると...</strong></p> <p><a href="https://crieit.now.sh/upload_images/f414647101ae6ff6105459a3b582bd3b6041b26740466.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f414647101ae6ff6105459a3b582bd3b6041b26740466.PNG?mw=700" alt="キャプチャ5.PNG" /></a><br /> <strong>"xtu"の"x"を入力した時点で”ッ”と"ク"を別々と考えて入力方法を置き換えています。</strong></p> </blockquote> <h2 id="プログラム"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0">プログラム</a></h2> <p>筆者なりに上記のシステムを再現しようとすると以下のようなプログラムになりました。</p> <h3 id="Kanji.txt"><a href="#Kanji.txt">Kanji.txt</a></h3> <blockquote> <p>タイピングゲーム内で使う文章をそのまま入力します。<br /> 一度のタイピングで入力する長さを決めて改行してください。</p> </blockquote> <pre><code>別に君を求めてないけど 横にいられると思いだす。 君のドルチェ&ガッパーナの その香水のせいだよ。 </code></pre> <h3 id="Japanese.txt"><a href="#Japanese.txt">Japanese.txt</a></h3> <blockquote> <p>Kanji.txtの内容を全てひらがなにして入力してください。</p> </blockquote> <pre><code>べつにきみをもとめてないけど よこにいられるとおもいだす。 きみのどるちぇあんどがっぱーなの そのこうすいのせいだよ。 </code></pre> <h3 id="KanjiFuri.txt"><a href="#KanjiFuri.txt">KanjiFuri.txt</a></h3> <blockquote> <p>Kanji.txt内の文字をひらがなにした時、何文字になるかを入力してください(ひらがなの場合は1)</p> </blockquote> <pre><code>21212111111 211111121111 2111113111111 1122111112 </code></pre> <h3 id="Dictionary.cs"><a href="#Dictionary.cs">Dictionary.cs</a></h3> <blockquote> <p>全ての入力方法を連想配列で整理しています。<br /> 下の方にある関数はKeyかValueを渡すことで紐づけられた要素を全て取り出すことができます</p> </blockquote> <pre><code>using System.Collections; using System.Collections.Generic; using UnityEngine; public class Dictionary : MonoBehaviour { //デフォルトの入力方法 public readonly Dictionary<string, string> dic = new Dictionary<string, string>() { {"あ", "a"},{"い", "i"},{"う", "u"},{"え", "e"},{"お", "o"}, {"か", "ka"},{"き", "ki"},{"く", "ku"},{"け", "ke"},{"こ", "ko"}, {"さ", "sa"},{"し", "si"},{"す", "su"},{"せ", "se"},{"そ", "so"}, {"た", "ta"},{"ち", "ti"},{"つ", "tu"},{"て", "te"},{"と", "to"}, {"な", "na"},{"に", "ni"},{"ぬ", "nu"},{"ね", "ne"},{"の", "no"}, {"は", "ha"},{"ひ", "hi"},{"ふ", "hu"},{"へ", "he"},{"ほ", "ho"}, {"ま", "ma"},{"み", "mi"},{"む", "mu"},{"め", "me"},{"も", "mo"}, {"や", "ya"},{"ゆ", "yu"},{"よ", "yo"}, {"ら", "ra"},{"り", "ri"},{"る", "ru"},{"れ", "re"},{"ろ", "ro"}, {"わ", "wa"},{"を", "wo"},{"ん", "n"}, {"が", "ga"},{"ぎ", "gi"},{"ぐ", "gu"},{"げ", "ge"},{"ご", "go"}, {"ざ", "za"},{"じ", "zi"},{"ず", "zu"},{"ぜ", "ze"},{"ぞ", "zo"}, {"だ", "da"},{"ぢ", "di"},{"づ", "du"},{"で", "de"},{"ど", "do"}, {"ば", "ba"},{"び", "bi"},{"ぶ", "bu"},{"べ", "be"},{"ぼ", "bo"}, {"ぱ", "pa"},{"ぴ", "pi"},{"ぷ", "pu"},{"ぺ", "pe"},{"ぽ", "po"}, {"ぁ","xa" },{"ぃ","xi" },{"ぅ","xu" },{"ぇ","xe" },{"ぉ","xo" }, {"っ", "xtu"}, {"ゃ","xya" },{"ゅ","xyu" },{"ょ","xyo"}, {"きゃ","kya"},{"きぃ","kyi"},{"きゅ","kyu"},{"きぇ","kye"},{"きょ","kyo"}, {"しゃ","sya"},{"しぃ","syi"},{"しゅ","syu"},{"しぇ","she"},{"しょ","syo"}, {"ちゃ","tya"},{"ちぃ","tyi"},{"ちゅ","tyu"},{"ちぇ","tye"},{"ちょ","tyo"}, {"にゃ","nya"},{"にぃ","nyi"},{"にゅ","nyu"},{"にぇ","nye"},{"にょ","nyo"}, {"ひゃ","hya"},{"ひぃ","hyi"},{"ひゅ","hyu"},{"ひぇ","hye"},{"ひょ","hyo"}, {"みゃ","mya"},{"みぃ","myi"},{"みゅ","myu"},{"みぇ","mye"},{"みょ","myo"}, {"りゃ","rya"},{"りぃ","ryi"},{"りゅ","ryu"},{"りぇ","rye"},{"りょ","ryo"}, {"ぎゃ","gya"},{"ぎぃ","gyi"},{"ぎゅ","gyu"},{"ぎぇ","gye"},{"ぎょ","gyo"}, {"じゃ","zya"},{"じぃ","zhi"},{"じゅ","zyu"},{"じぇ","zye"},{"じょ","zyo"}, {"ぢゃ","dya"},{"ぢぃ","dyi"},{"ぢゅ","dyu"},{"ぢぇ","dye"},{"ぢょ","dyo"}, {"びゃ","bya"},{"びぃ","byi"},{"びゅ","byu"},{"びぇ","bye"},{"びょ","byo"}, {"てゃ","tha"},{"てぃ","thi"},{"てゅ","thu"},{"てぇ","the"},{"てょ","tho"}, {"うぁ","wha"},{"うぃ","whi"},{"うぇ","whe"},{"うぉ","who"}, {"でゃ","dha"},{"でぃ","dhi"},{"でゅ","dhu"},{"でぇ","dhe"},{"でょ","dho"}, {"くぁ","qa"},{"くぃ","qi"},{"くぇ","qe"},{"くぉ","qo"}, {"ふぁ","fa"},{"ふぃ","fi"},{"ふぇ","fe"},{"ふぉ","fo"}, {"ヴぁ","va"},{"ヴぃ","vi"},{"ヴ","vu"},{"ヴぇ","ve"},{"ヴぉ","vo"}, {"ぴゃ","pya"},{"ぴぃ","pyi"},{"ぴゅ","pyu"},{"ぴぇ","pye"},{"ぴょ","pyo"}, {"、","," },{"。","."},{"「","["},{"」","]"}, }; //デフォルトではない入力方法 public readonly Dictionary<string, string> Epicdic = new Dictionary<string, string>() { {"ca","か" },{"ci","し" },{"cu","く" },{"ce","せ" },{"co","こ" }, {"cha","ちゃ"},{"chi","ち"},{"chu","ちゅ"},{"che","ちぇ"},{"cho","ちょ"}, {"cya","ちゃ"},{"cyi","ちぃ"},{"cyu","ちゅ"},{"cye","ちぇ"},{"cyo","ちょ"}, {"fu","ふ"}, {"ja","じゃ"},{"ji","じ"},{"ju","じゅ"},{"je","じぇ"},{"jo","じょ"}, {"la","ぁ" },{"li","ぃ" },{"lu","ぅ" },{"le","ぇ" },{"lo","ぉ" }, {"lya","ゃ" },{"lyu","ゅ" },{"lyo","ょ" }, {"ltu", "っ"}, {"nn","ん" }, {"qu","く" },{"qyi","くぃ"},{"qye","くぇ"}, {"sha","しゃ" },{"shi","し"},{"shu","しゅ"},{"she","しぇ"},{"sho","しょ"}, {"tsu","つ"}, {"yi","い"},{"ye","え"}, }; //渡した要素と紐づいている要素をdicから探し、リストにして返します。 public KeyValuePair<string, string> SearchdicKey(string key) { var dicpair = new KeyValuePair<string, string>(); foreach (KeyValuePair<string, string> pair in dic) { if (key == pair.Key) { dicpair = pair; } } return dicpair; } //渡した要素と紐づいている要素をdicから探し、リストにして返します。 public KeyValuePair<string, string> SearchdicValue(string value) { var dicpair = new KeyValuePair<string, string>(); foreach (KeyValuePair<string, string> pair in dic) { if (value == pair.Value) { dicpair = pair; } } return dicpair; } //渡した要素と紐づいている要素を両方のdicから探し、リストにして返します。 public List<KeyValuePair<string, string>> SearchTotaldicKey(string key) { var dicpair = new List<KeyValuePair<string, string>>(); foreach (KeyValuePair<string, string> pair in Epicdic) { if (key == pair.Value) { dicpair.Add(new KeyValuePair<string, string>(pair.Value, pair.Key)); } } foreach (KeyValuePair<string, string> pair in dic) { if (key == pair.Key) { dicpair.Add(pair); } } return dicpair; } //渡した要素と紐づいている要素を両方のdicから探し、リストにして返します。 public List<KeyValuePair<string, string>> SearchTotaldicValue(string value) { var dicpair = new List<KeyValuePair<string, string>>(); foreach (KeyValuePair<string, string> pair in Epicdic) { if (value == pair.Value) { dicpair.Add(pair); } } foreach (KeyValuePair<string, string> pair in dic) { if (value == pair.Key) { dicpair.Add(new KeyValuePair<string, string>(pair.Value, pair.Key)); } } return dicpair; } } </code></pre> <h3 id="SetList.cs"><a href="#SetList.cs">SetList.cs</a></h3> <blockquote> <p>次にタイピングするKanjij.txtと同じ行にあるJapanese.txtとKanjiFuri.txtの文字列をSetLists()に渡し、それぞれを区分しながらListに要素を追加していきます。</p> <p>・Japanese.txtは一度に入力可能でありながら、文字列が最長になるように区分し、FuriListに追加していきます。<br /> ・KanjiFuri.txtは1つの数字ごとに文字列を区分しながらKanjiFuriListに追加していきます。<br /> ・また、FuriListに要素を追加しながらDictionary.dicから入力方法を取得し、RomaListに追加していきます。</p> </blockquote> <pre><code>using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SetList : MonoBehaviour { private SetText ST; private Dictionary Dic; public List<string> RomaList; public List<string> FuriList; public List<int> KanjiFuriList; void Awake() { ST = GetComponent<SetText>(); Dic = GetComponent<Dictionary>(); } private string FuriStr = ""; private string RomaStr = ""; //FuriStrとRomaStrの内容をそれぞれのリストに入れて初期化する。 private void AddList() { FuriList.Add(FuriStr); RomaList.Add(RomaStr); FuriStr = ""; RomaStr = ""; } //渡されたJapanese.txtの一行を一文字づつ仕分けるよう public void SetLists(string Roma , string KanjiFuri) { for(int i = 0; i < Roma.Length - 1; i++) { var chr = Roma[i].ToString(); switch (chr) { case "っ": FuriStr += chr; if (i != Roma.Length - 1) { switch(Roma[i + 1].ToString()) { case "あ": case "い": case "う": case "え": case "お": case "な": case "に": case "ぬ": case "ね": case "の": case "ん": RomaStr += Dic.SearchdicKey(Roma[i + 1].ToString()).Value; AddList(); break; default: RomaStr += Dic.SearchdicKey(Roma[i + 1].ToString()).Value.Substring(0,1); break; } } else { FuriStr += chr; AddList(); } break; case "ぁ": case "ぃ": case "ぅ": case "ぇ": case "ぉ": case "ゃ": case "ゅ": case "ょ": if (i != 0 && FuriStr != "") { if (Dic.dic.ContainsKey(FuriStr + chr)) { FuriStr += chr; RomaStr += Dic.SearchdicKey(Roma[i - 1].ToString() + chr).Value; AddList(); } else { AddList(); FuriStr += chr; RomaStr += Dic.SearchdicKey(chr).Value; AddList(); } } else { FuriStr += chr; RomaStr += Dic.SearchdicKey(chr).Value; AddList(); } break; case "ん": FuriStr += chr; RomaStr += Dic.SearchdicKey(chr).Value; if (i != Roma.Length - 1) { switch(Roma[i + 1].ToString()) { case "あ": case "い": case "う": case "え": case "お": case "な": case "に": case "ぬ": case "ね": case "の": case "ん": case "や": case "ゆ": case "よ": case "ゃ": case "ゅ": case "ょ": RomaStr += "n"; break; } } else { RomaStr += "n"; } AddList(); break; default: if (i != Roma.Length - 1) { switch (Roma[i + 1].ToString()) { case "ぁ": case "ぃ": case "ぅ": case "ぇ": case "ぉ": case "ゃ": case "ゅ": case "ょ": FuriStr += chr; break; default: FuriStr += chr; RomaStr += Dic.SearchdicKey(chr).Value; AddList(); break; } } else { FuriStr += chr; RomaStr += Dic.SearchdicKey(chr).Value; AddList(); } break; } } int num = 0; for(int i = 0; i < KanjiFuri.Length - 1; i++) { num += KanjiFuri[i] - '0'; KanjiFuriList.Add(num); } } } </code></pre> <h3 id="SetText.cs"><a href="#SetText.cs">SetText.cs</a></h3> <blockquote> <p>txtファイルや表示中の文字列の行数、打ち終わった字数などを管理しています。</p> </blockquote> <pre><code>using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SetText : MonoBehaviour { private SetList SL; private TypingSystem TS; [SerializeField] private TextAsset Furigana = null; //ここにKanjiFuri.txtを入れる [SerializeField] private TextAsset Hiragana = null; //ここにJapanese.txtを入れる [SerializeField] private TextAsset Kanji = null; //ここにKanji.txtを入れる [SerializeField] private List<string> FuriList = new List<string>(); [SerializeField] private List<string> HiraList = new List<string>(); [SerializeField] private List<string> KanjiList = new List<string>(); [SerializeField] private Text RomaText = null; //ここにKanjiFuri.txtの内容を表示するためのテキストを入れる [SerializeField] private Text FuriText = null; //ここにJapanese.txtの内容を表示するためのテキストを入れる [SerializeField] private Text KanjiText = null; //ここにKanji.txtの内容を表示するためのテキストを入れる [SerializeField] private int ColumunNum = -1; //現在打っている文字列の行数、最初に+1するため最初は-1にする private int SetColumunNum { get { return ColumunNum; } set { ColumunNum = value; SL.SetLists(HiraList[ColumunNum],FuriList[ColumunNum]); KanjiProp = 0; ListProp = 0; FuriProp = 0; RomaProp = 0; } } public int Romanum { get; private set; } public int Furinum { get; private set; } public int Kanjinum { get; private set; } public int Listnum { get; private set; } //メインの文字の設定 private int KanjiProp { get { return Kanjinum; } set { Kanjinum = value; var kanjifuristr = "<color=\"red\">"; kanjifuristr += KanjiList[ColumunNum].Insert(Kanjinum, "</color>"); KanjiText.text = kanjifuristr; } } //フリガナの文字の設定 private int FuriProp { get { return Furinum; } set { Furinum = value; var furistr = "<color=\"red\">"; furistr += HiraList[ColumunNum].Insert(Furinum, "</color>"); FuriText.text = furistr; while (Furinum >= SL.KanjiFuriList[Kanjinum]) { KanjiProp++; } } } //SetList.FuriListの何個目の要素を打っているか private int ListProp { get { return Listnum; } set { if(value == SL.FuriList.Count) { ResetText(); return; } else if(value != 0) { FuriProp += SL.FuriList[Listnum].Length; } Listnum = value; } } //ローマ字の設定 public int RomaProp { get { return Romanum; } set { Romanum = value; if (Romanum == SL.RomaList[Listnum].Length) { ListProp++; TS.ResetTypedStr(); RomaProp = 0; } else { var romastr = "<color=\"red\">"; for (int i = 0; i < SL.RomaList.Count; i++) { var str = SL.RomaList[i]; if (i != Listnum) { romastr += str; } else { romastr += str.Insert(Romanum, "</color>"); } } RomaText.text = romastr; } } } private void Start() { SL = GetComponent<SetList>(); TS = GetComponent<TypingSystem>(); SetStrList(Hiragana, HiraList); SetStrList(Furigana, FuriList); SetStrList(Kanji, KanjiList); ResetText(); void SetStrList(TextAsset textAsset, List<string> list) { var array = textAsset.text.Split('\n'); list.AddRange(array); } } //テキストを初期化する private void ResetText() { SL.FuriList.Clear(); SL.RomaList.Clear(); SL.KanjiFuriList.Clear(); SetColumunNum++; } } </code></pre> <h3 id="TypingSystem.cs"><a href="#TypingSystem.cs">TypingSystem.cs</a></h3> <blockquote> <p>txtファイルや表示中の文字列の行数、打ち終わった字数などを管理しています。</p> </blockquote> <pre><code>using System.Collections; using System.Collections.Generic; using UnityEngine; public class TypingSystem : MonoBehaviour { private SetList SL; private SetText ST; private Dictionary Dic; private string Typedstr; public void ResetTypedStr() { Typedstr = ""; } private void Awake() { ST = GetComponent<SetText>(); SL = GetComponent<SetList>(); Dic = GetComponent<Dictionary>(); } string[] keys = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "-", ",",".","[","]"}; //keys内にあるキーを打った時 void Update() { foreach (string key in keys) { if (Input.GetKeyDown(key)) { TypeJudge(key); } } } public void TypeJudge(string type) { //打っている途中の文字と現在打った文字をつなげる var JudgeStr = Typedstr + type; bool jud = false; //打った文字が n で 前の文字が nn じゃない場合 if (type == "n" && SL.RomaList[ST.Listnum] == "n") { SL.RomaList[ST.Listnum] = "nn"; RightEnter(); return; } //正しく打てている場合 if (type == SL.RomaList[ST.Listnum][ST.RomaProp].ToString()) { RightEnter(); } else { //Dictionaryの中のEpicDic内の文字を打とうとしている場合 for (int i = SL.FuriList[ST.Listnum].Length; i != 0 && !jud ; i--) { var SearchStr = SL.FuriList[ST.Listnum].Substring(0,i); if (i != 1 && SL.FuriList[ST.Listnum][0].ToString() == "っ") { SearchStr = SearchStr.Substring(1, SearchStr.Length-1); } var list = Dic.SearchTotaldicKey(SearchStr); foreach (var d in list) { var MatchStr = ""; if (i != 1 && SL.FuriList[ST.Listnum][0].ToString() == "っ") { MatchStr += d.Value.Substring(0, 1); } if (JudgeStr.Length <= MatchStr.Length + d.Value.Length && !jud) { MatchStr += d.Value; if (JudgeStr == MatchStr.Substring(0, JudgeStr.Length)) { //print(MatchStr + "で見つかりました"); TypeMatch(i , MatchStr); } } } } //該当した文字が無い場合(ミスタイプしている場合) if(!jud) { MissEnter(); } } //EpicDic内にある文字が正しく打てている時に呼び出される //打った文字とまだ打っていない文字を切り分ける作業をする void TypeMatch(int num, string MatchStr) { jud = true; var Furistr = SL.FuriList[ST.Listnum]; SL.FuriList[ST.Listnum] = Furistr.Substring(0, num); SL.RomaList[ST.Listnum] = MatchStr; if(Furistr.Length != num) { SL.FuriList.Insert(ST.Listnum + 1, Furistr.Substring(num)); SL.RomaList.Insert(ST.Listnum + 1, Dic.SearchdicKey(SL.FuriList[ST.Listnum + 1]).Value); } Typedstr = JudgeStr; RightEnter(); } //ミスタイプしたとき void MissEnter() { print("ミスしました!"); } //正しく打てているとき void RightEnter() { Typedstr = JudgeStr; ST.Romanum++; } } } </code></pre> <h2 id="実際に動かしてみる"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AB%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">実際に動かしてみる</a></h2> <p>上にあるプログラムを全て同じオブジェクトにアタッチし、以下の画像のように設定すると動きます。<br /> <a href="https://crieit.now.sh/upload_images/003fafc46d6d8a6b98c5c0b37c69efcc6042d4410d2b1.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/003fafc46d6d8a6b98c5c0b37c69efcc6042d4410d2b1.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <h2 id="完成した作品"><a href="#%E5%AE%8C%E6%88%90%E3%81%97%E3%81%9F%E4%BD%9C%E5%93%81">完成した作品</a></h2> <p>そしてこのプログラムを組み込んで実際に完成した作品がこちらになります。</p> <blockquote> <h5 id="ケロツグ -雨乞いタイピング-"><a href="#%E3%82%B1%E3%83%AD%E3%83%84%E3%82%B0+-%E9%9B%A8%E4%B9%9E%E3%81%84%E3%82%BF%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0-">ケロツグ -雨乞いタイピング-</a></h5> <p><a target="_blank" rel="nofollow noopener" href="https://game.nicovideo.jp/atsumaru/games/gm13342">Webからのプレイはこちらから。</a><br /> <a target="_blank" rel="nofollow noopener" href="https://drive.google.com/drive/folders/1rJD0wXc3cIBas-bnDUSY_-UlANOCqGbo?usp=sharing">プロジェクトのダウンロードはこちらから</a></p> </blockquote> <p>芥川龍之介の「蛙」の文をひたすら打つタイピングゲームです。<br /> 文字を打つたびに蛙が増えていき、正確なタイピングを続けると制限時間が増えたりします。</p> <h2 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h2> <p>作るのは苦戦しましたが内容は結構シンプルな構造になっていると思います。<br /> この方法で作っていると対応していないキーが出てくると思います。<br /> 見つけた場合はDisctionary.csの方に書き足して頂けると対応されると思います。</p> <p>最後まで読んでいただきありがとうございました。</p> かの tag:crieit.net,2005:PublicArticle/16711 2021-03-02T20:29:59+09:00 2021-03-02T20:29:59+09:00 https://crieit.net/posts/csharp-strangeness-in-a-simple-using-statement 【C#】「単純なusingステートメント」の違和感 <p><code>using</code>は外部リソースを確実に解放するために用いる。</p> <p>たとえば、</p> <pre><code class="csharp">using (var sw = new StringWriter(filePath, append: true, Encoding.UTF8)) { sw.Write(hoge); } </code></pre> <p>と書けば、スコープを抜けた時点でファイルが確実に解放される。いちいち<code>Close</code>とか<code>Dispose</code>とかしなくて済むのだ。これは素晴らしい。</p> <p>処理が単一ステートメントであれば<code>{}</code>は省略できるので、</p> <pre><code class="csharp">using (var sw = new StringWriter(filePath, append: true, Encoding.UTF8)) sw.Write(hoge); </code></pre> <p>このように書ける。これも全く同じ意味で、<code>if</code>の<code>{}</code>を省略しているのと変わらない。ので、今までこう書いていたが、C#8.0では以下のようにメッセージが表示される。</p> <blockquote> <p>IDE0063:'using'ステートメントは単純にできます</p> </blockquote> <p>推奨の修正を適用すると、</p> <pre><code class="csharp">using (var sw = new StringWriter(filePath, append: true, Encoding.UTF8)); sw.Write(hoge); </code></pre> <p>となる。これも動くんだけど違和感がすごい。なんというか、<code>using</code>の後に<code>{}</code>があればスコープはそこに限定されるし、なければ次の単一処理がスコープになっているとはっきりわかる。</p> <p>でもこれ、</p> <pre><code class="csharp">using (var sw = new StringWriter(filePath, append: true, Encoding.UTF8)); sw.Write(hoge); sw.Write(fuga); </code></pre> <p>ってかけるんだよね。で、<code>fuga</code>も書き込まれるんだよね。じゃあスコープはいつまで有効なの?というと、その変数の範囲らしい。</p> <p>個人的には使わなくなったらとっとと<code>Dispose</code>しないと気持ち悪いので、IDEの警告を抑制して無視して、従来通り<code>{}</code>を必要に応じて使ってスコープを明記することにした。もっとも、そこだけメソッドに切り出して単純な<code>using</code>を使うのが一番スマートなのかもしれないが。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16589 2021-01-13T20:55:23+09:00 2021-01-14T18:55:08+09:00 https://crieit.net/posts/what-should-be-written-in-the-comment 【C#】コメントに書くこと、書かないこと <p>※C#としているがC#以外でも同じで、サンプルコードをC#で書いているのでタイトルにC#とつけた。</p> <p>プログラミングを行う上で、コメントの重要性は大きい。適切なコメントを書けばコードを読む人の助けになる。逆に、どうでもいいコメントや嘘のコメントを書けばそれは悪影響をもたらす。</p> <p>コメントに関しては宗教のようなものなので初めに私のポリシーを明記しておく。この記事の内容もこれに沿って展開する。</p> <ul> <li>コードを読んだら秒でわかることはコメントに書かない</li> <li>コードを読んだらわかるが読むのに時間がかかりそうな時は書く</li> <li>なぜこのようなロジックになっているかを書く</li> <li>拡張・修正する際に、罠になりそうな箇所の警告を書く</li> <li>コメントで説明するくらいなら命名を見直す</li> <li>変更前のソースをコメントアウトして残さない(論外)</li> </ul> <p>この記事の内容はあくまで私が考えていることの表明であって、「私の意見が絶対的に正しい」とか「コメントはこう書かれるべきである」という趣旨の内容ではない。念のため書いておく。</p> <h1 id="コードを読んだら秒でわかるようなことはコメントに書かない"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E3%82%89%E7%A7%92%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%88%E3%81%86%E3%81%AA%E3%81%93%E3%81%A8%E3%81%AF%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AB%E6%9B%B8%E3%81%8B%E3%81%AA%E3%81%84">コードを読んだら秒でわかるようなことはコメントに書かない</a></h1> <p>正確に言うと、書く必要がない。なにせ読んだら秒でわかるのだから。</p> <p>極端な例だが、以下のようなコメントは不要だ。</p> <pre><code class="csharp">// 〇〇さん(xx) var nameAndAge = $"{name}さん({age})"; </code></pre> <p>これは、変数を「〇〇さん(xx)」の形に整形しているが、<strong>そんなもん見たらわかる</strong>のである。</p> <p>見たらわかることをいちいちコメントに書く必要はない。何故か?</p> <p>それは、ソースを修正する時にコメントも修正する必要が出てくるからだ。</p> <p>上記コードを以下のように修正したとしよう。</p> <pre><code class="csharp">// 〇〇さん(xx) var nameAndAge = $"{name}さん({age}歳)"; </code></pre> <p>コードは修正したが、コメントの修正を忘れたとする。するとどうなるか。</p> <p>ソースを見ると「〇〇さん(xx歳)」となっているが、コメントは「〇〇さん(xx)」となっている。これを見た人は、「どっちが正しい仕様なんだろう?」と頭を悩ませるのである。そして仕様を確認するという手間が発生するし、コメントを修正する(あるいは修正を指示する)という手間も発生することだろう。</p> <p>書いていなければ「ふーん、『〇〇さん(xx歳)』の形式ね。」とスムーズに読めるのに、コメントがコードの内容と違っているばかりに余計な手間がかかる。これは完全に悪である。</p> <p>「いやいや、普通はコードと一緒にコメントも修正するでしょ」と言う人もいるかもしれないが、そういう普通の人と一緒に働いてみたい。特にコード変更に対して充分な時間が与えられないことは往々にしてあり、急いで修正、テスト通るかのチェックをしてレビュアーに投げるというものだろうが、経験上ほとんどのレビュアーは差異が発生している部分しかチェックしない。</p> <p><strong>コメントが実装と違っていてもテストは通るし、人の目のチェックも通る時は通る</strong>のだ。だからこそ、コメントは常に正確に書いていなければそもそも書く意味がないのだ。コードを変えたならコメントもそれに合わせて変えなければならないのだ。</p> <p>でも人間はそんな完璧じゃないから絶対にどこかで修正漏れが発生する。だったら初めから「コードを読んだら秒でわかる」ような箇所のコメントは不要なのだ。</p> <h1 id="コードを読んだらわかるが読むのに時間がかかりそうな時は書く"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E3%82%89%E3%82%8F%E3%81%8B%E3%82%8B%E3%81%8C%E8%AA%AD%E3%82%80%E3%81%AE%E3%81%AB%E6%99%82%E9%96%93%E3%81%8C%E3%81%8B%E3%81%8B%E3%82%8A%E3%81%9D%E3%81%86%E3%81%AA%E6%99%82%E3%81%AF%E6%9B%B8%E3%81%8F">コードを読んだらわかるが読むのに時間がかかりそうな時は書く</a></h1> <p>以下のようなコード。</p> <pre><code class="csharp">public static List<string> Nabeatsu() { var nabe = new List<string>(); foreach (var n in Enumerable.Range(1, 40)) if ((n % 3 == 0) || $"{n}".Contains("3")) nabe.Add("アホ"); else nabe.Add($"{n}"); return nabe; } </code></pre> <p>これは、「1から40までの自然数のうち、3の倍数または3のつく数字の時は「アホ」を、そうでない時はその数字を文字列型のListに入れて返す」という挙動の<code>Nabeatsu</code>というメソッドである。</p> <p>上記のことは、コードを読めば分かるが意図を正確に把握するにはを全て読まなければならない。</p> <p>こういうコードは処理が簡単に書いてあれば便利だ。メソッドであればXMLコメントとして残すべきだ。</p> <p>Visual Studioを使っているならば、メソッドの上に<code>///</code>と入力するだけでXMLコメントの雛形が自動で作成される。</p> <pre><code class="csharp">/// <summary> /// /// </summary> /// <returns></returns> public static List<string> Nabeatsu() { var nabe = new List<string>(); foreach (var n in Enumerable.Range(1, 40)) if ((n % 3 == 0) ** $"{n}".Contains("3")) nabe.Add("アホ"); else nabe.Add($"{n}"); return nabe; } </code></pre> <p>あとは、コメントを書いていくだけだ。</p> <pre><code class="csharp">/// <summary> /// 1から40までの自然数のうち、3の倍数または3のつく数字の時は「アホ」を、そうでない時はその数字を文字列型のListに入れて返す /// </summary> /// <returns>そのまま使えるナベアツリスト</returns> public static List<string> Nabeatsu() { var nabe = new List<string>(); foreach (var n in Enumerable.Range(1, 40)) if ((n % 3 == 0) ** $"{n}".Contains("3")) nabe.Add("アホ"); else nabe.Add($"{n}"); return nabe; } </code></pre> <p>このようにXMLコメントを書いておくと、<br /> <a href="https://crieit.now.sh/upload_images/65effe08f1a79b8612a3b12b55d32f4d5ffedf697abff.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/65effe08f1a79b8612a3b12b55d32f4d5ffedf697abff.jpg?mw=700" alt="image" /></a><br /> のように、呼び出し元でメソッド名をマウスオーバーすると参照できるようになる。</p> <h1 id="なぜこのようなロジックになっているかを書く"><a href="#%E3%81%AA%E3%81%9C%E3%81%93%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E3%82%92%E6%9B%B8%E3%81%8F">なぜこのようなロジックになっているかを書く</a></h1> <p>コードを読んでいると、「ここはなんでこう書いてるの?」と思うことがある。その疑問に先回りして「なぜか」が書いてあるコメントは良いコメントだと思う。</p> <p>やや乱暴な例だが、例えば以下のようなコード。</p> <pre><code class="csharp">user.UpdatePasswordTo(plainPassword); </code></pre> <p>これは恐らく、「パスワードを引数の文字列で更新している」という処理だろう。</p> <p>しかし<code>plainPassword</code>は見るからに平文のパスワードだ。こんなもんそのまま更新する訳にはいかない。</p> <p>このコード、実は<code>UpdatePasswordTo</code>内部で引数の文字列はハッシュ化するという処理が実行されることになっている。だからここは別に平文で渡しても問題ないのだ。</p> <p>だがそんなことはどこにも書いてない。<code>UpdatePasswordTo</code>の実装を見るまでは分からないのである。</p> <p>そこで、</p> <pre><code class="csharp">user.UpdatePasswordTo(plainPassword); // 中でハッシュ化するから平文でOK </code></pre> <p>というコメントがあったとしたらどうだろうか。それなら問題ないな、と疑問がすぐ解決するのではないだろうか。</p> <p>もちろん、そのコメントが嘘であってはいけない(つまり本当は中でハッシュ化などしていない)が、こういうコメントはそれが事実である限りとても役に立ち、コードリーディングを助けるものになる。</p> <h1 id="拡張・修正する際に、罠になりそうな箇所の警告を書く"><a href="#%E6%8B%A1%E5%BC%B5%E3%83%BB%E4%BF%AE%E6%AD%A3%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AB%E3%80%81%E7%BD%A0%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%9D%E3%81%86%E3%81%AA%E7%AE%87%E6%89%80%E3%81%AE%E8%AD%A6%E5%91%8A%E3%82%92%E6%9B%B8%E3%81%8F">拡張・修正する際に、罠になりそうな箇所の警告を書く</a></h1> <p>これも余りいい例が思い浮かばないのでちょっと強引な例だが書いてみる。</p> <p>あるシステムがあり、利用者には個別の<code>userId</code>を自分で設定してもらう。<code>userId</code>は"@"以外の文字列で任意に設定できるが、実は使用しているライブラリのバグで"@"から始まる文字列を登録しようとすると実行時エラーになる。が、現在の仕様だとそもそも"@"は入ってこないため特に問題はなかったとする。</p> <p>ここで"@"をIDに含めたいというユーザーの要望により、ユーザーIDに"@"を含められるように仕様変更することになった。"agasahakase"というユーザーがカッコつけて"@g@s@h@k@se"というIDにしたいと言い出したのだ。</p> <p>仕様変更しリリースすると、"agasahakase"さんは"@g@s@h@k@se"にIDを変更していた。別のユーザーで、TwitterライクなIDにしたいと思い"apashoni"から"@apashoni"に変更しようとしたユーザーは更新に失敗してしまった。</p> <p>急いで原因を調査するが、何も分からない。コードを書いた人は既に転職しており誰も原因がわからない。三日三晩寝ずに調べて、とうとう"@"で始まる文字列はIDに設定できないということが判明した。</p> <p>もしここで、</p> <pre><code class="csharp">user.UpdateIdTo(newId); // 現行仕様では関係ないが、ライブラリのバグで"@"で始まるIDは登録できない </code></pre> <p>などと一言添えてあればそれを見越したリリース案内が出来たのである。</p> <p>まぁ上記のはかなり強引な一例であり実際にここまで書くかは議論の分かれるところかもしれないが、「罠になりそうあ箇所の警告を書く」とは上記のような話だ。</p> <p>現行仕様や近々に何かに影響する訳ではないが、将来的に役立つかもしれないことを書いておくのも良い。</p> <p>繰り返すが上記はかなり強引な例えだ(いい例が思いつかなかった)。</p> <h1 id="コメントで説明するくらいなら命名を見直す"><a href="#%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%81%A7%E8%AA%AC%E6%98%8E%E3%81%99%E3%82%8B%E3%81%8F%E3%82%89%E3%81%84%E3%81%AA%E3%82%89%E5%91%BD%E5%90%8D%E3%82%92%E8%A6%8B%E7%9B%B4%E3%81%99">コメントで説明するくらいなら命名を見直す</a></h1> <p><a href="https://crieit.net/posts/csharp-naming">【C#】命名って大事だね</a>という記事でも書いたが、不足情報を補うくらいなら命名やロジックで直感的に説明しコメントは書かないという方がよほど親切だ。理由は前述した通り、コメントもメンテナンスが必要になるため。</p> <p>例えば以下のようなコード。</p> <pre><code class="csharp">// 単価 decimal price = 100; // 単位はmm decimal height = 1700; </code></pre> <p>これらは</p> <pre><code class="csharp">decimal unitPrice = 100; decimal height_mm = 1700; </code></pre> <p>という命名にすれば、単価だということや単位がmmだということは一目瞭然だ。コメントを書く前に、まずこれらができないか試すべきだと個人的には思う。</p> <h1 id="変更前のソースをコメントアウトして残さない(論外)"><a href="#%E5%A4%89%E6%9B%B4%E5%89%8D%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%92%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%82%A2%E3%82%A6%E3%83%88%E3%81%97%E3%81%A6%E6%AE%8B%E3%81%95%E3%81%AA%E3%81%84%EF%BC%88%E8%AB%96%E5%A4%96%EF%BC%89">変更前のソースをコメントアウトして残さない(論外)</a></h1> <p>これ、最近のエンジニアさんはおとぎ話くらいに思うのだろうか…?</p> <p>これらは、バージョン管理ソフトが普及していなかった頃の名残だ(と思う)。昔はバージョン管理ソフトなんていう便利なものはなかったので、こうやって修正前のコードを残しておく必要があった。</p> <p>しかし現在はだいたいどのチームでもgitやSubVersionなんかを使ってバージョン履歴管理やソースコードの共有などをしているのであり、現代ではほとんど不要なハックと言える。読んでいて邪魔になるだけだし、そもそもコメントアウトを外してもまともに動作しない。</p> <p>残念ながら未だにこういうコードは見かけるし、こういう風にコメントを残す人もいる。特に大昔からコード書いている人なんかは、よくこういうコメントアウトをしていらっしゃる。恐らくバージョン管理ソフトの使い方などを分かっていないんだと思うが、だったら勉強とかちゃんとしてほしいなと正直思う。勉強できない古のエンジニアはとっとと解雇して私の給料増やしてちょと思うのだがなかなかそうもいかないので難しいところだ。</p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p>以上が、私がコメントを書くときに気を付けているような内容だ。ここには書いていないが意識していることも多分あると思う。</p> <p>繰り返しになるが、あくまで私個人の考えで合ってこれが正しいという訳ではないが、まぁ別にそこまで変なことを書いているつもりもないので参考になるところをつまみ食いで参考にしてもらえればいいなと思う。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16578 2021-01-11T21:48:56+09:00 2021-01-11T21:48:56+09:00 https://crieit.net/posts/csharp-dry-oaoo 【C#】DRYとOAOO <h1 id="DRY原則とは"><a href="#DRY%E5%8E%9F%E5%89%87%E3%81%A8%E3%81%AF">DRY原則とは</a></h1> <p>知っている人も多いと思うが、<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY原則</a>というものがある。</p> <p><strong>D</strong>on't <strong>R</strong>epeat <strong>Y</strong>ourselfの頭文字をとった言葉で、簡単に言うと「同じことを繰り返すな」という意味。</p> <h1 id="サンプルコード"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89">サンプルコード</a></h1> <p>例を出してみる。</p> <pre><code class="csharp">public class Person { public string FirstName { set; get; } = ""; public string LastName { set; get; } = ""; public string FullName { set; get; } = ""; } </code></pre> <p>上記のような<code>Person</code>クラスのインスタンスを作成するとしよう。</p> <pre><code class="csharp">var haruki = new Person(); haruki.FirlstName = "Haruki"; haruki.LastName = "Yachizaki"; haruki.FullName = "Yachizaki Haruki"; Console.Write(haruki.FullName); // 結果 -> Yachizaki Haruki </code></pre> <p>FullNameを出力すると、"Yachizaki Haruki"となる。</p> <p>上記のクラスには問題がある。</p> <p>それは、<code>FirstName</code>と<code>LastName</code>を設定して、<code>FullName</code>の設定が漏れていた場合に、<code>FullName</code>が出力されないという問題だ。</p> <pre><code class="csharp">var haruki = new Person(); haruki.FirstName = "Haruki"; haruki.LastName = "Yachizaki"; // haruki.FullName の設定を忘れている Console.Write(haruki.FullName); // 結果 -> ""(空白) // PersonクラスのFullNameのデフォルト値が表示される </code></pre> <p>設定されるべき値を設定していなかったのだから当然の挙動だが、そもそも一般的に<code>FullName</code>は<code>FirstName</code>と<code>LastName</code>さえ分れば求められるにも関わらず、個別に設定しなければならない方がおかしいと言えるだろう。<code>FullName</code>の設定を繰り返しているとも言える。</p> <p>そこで、<code>FullName</code>を修正し、以下のようにしてみる。</p> <pre><code class="csharp">public class Person { public string FirstName { set; get; } = ""; public string LastName { set; get; } = ""; public string FullName => $"{LastName} {FirstName}"; } </code></pre> <p><code>FullName</code>は個別に設定するのではなく、<code>FirstName</code>と<code>LastName</code>を組み合わせて返すように変更する。</p> <p>すると、</p> <pre><code class="csharp">var haruki = new Person(); haruki.FirstName = "Haruki"; haruki.LastName = "Yachizaki"; // haruki.FullName は設定しない(setterを持たないのでそもそもできない) Console.Write(haruki.FullName); // 結果 -> Yachizaki Haruki </code></pre> <p>となる。</p> <p>他のプロパティから求められるプロパティは個別に用意しないという考え方では、DB設計などでも用いられる。DB設計においては、主にパフォーマンスの面から冗長なフィールドを用意することもあるが(<code>価格</code>と<code>税率</code>と<code>税込み価格</code>を持っておくなど)、プログラミングのソースコードにおいてはそこまで意識することは少ないと言えると思う(※ぱっと例が思いつかないが、冗長な実装をした方がいいケースもきっとあると思う)。</p> <p>プロパティが増えれば増えるほど管理しなければいけない情報が増え、バグの元になる。</p> <h1 id="メリット・デメリット"><a href="#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%83%BB%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">メリット・デメリット</a></h1> <p>メリット</p> <ul> <li>テストの箇所を集約できる <ul> <li>複数個所に書いていればその分テストが必要だが、一か所であればそれだけでカバーできる</li> </ul></li> <li>ロジックの改善や仕様変更の際に修正の手間が激減する <ul> <li>トレースログを仕込む時などに便利</li> </ul></li> </ul> <p>デメリット</p> <ul> <li>変更が呼び出し箇所全てに反映されるため、影響範囲を把握しておく必要がある</li> </ul> <h1 id="OAOO"><a href="#OAOO">OAOO</a></h1> <p>DRY原則とよく似た考え方に、OAOO(<strong>O</strong>nce <strong>A</strong>nd <strong>O</strong>nly <strong>O</strong>nce:ただ一度だけ書く)がある。</p> <p>例を示す。下記のようなコードがあるとする。</p> <pre><code class="csharp">var message1 = "あけましておめでとうございます"; var message2 = "今年もよろしくお願いします。"; var newMessage1 = ""; var newMessage2 = ""+ const string Dot = "。"; if (message1.EndsWith(Dot)) newMessage1 = message1; else newMessage1 = message1 + Dot; if (message2.EndsWith(Dot)) newMessage2 = message2; else newMessage2 = message2 + Dot; Console.Write(newMessage1 + newMessage2); // 結果 -> あけましておめでとうございます。今年もよろしくお願いします。 </code></pre> <p>上記コードは2つのメッセージを連結して出力するが、各メッセージが読点(<code>。</code>)で終わっていなければ付け足した後に連結するということをしている。</p> <p>この、「読点がなければ付け足す」という処理は<code>message1</code>に対して行うか<code>message2</code>に対して行うかだけの違いだが、2回に渡って書かれている。</p> <p>これにOAOOを当てはめると、以下のようにできる。</p> <pre><code class="csharp">// 以下のメソッドを用意 static string AppendDotIfNeeded(string message) { const string Dot = "。"; // nullチェックは省略 if (message.EndsWith(Dot)) return message; else return message + Dot; } // メソッドを用意すると以下のように書ける var message1 = "あけましておめでとうございます"; var message2 = "今年もよろしくお願いします。"; var message1EndsWithDot = AppendDotIfNeeded(message1); var message2EndsWithDot = AppendDotIfNeeded(message2); Console.Write(newMessage1 + newMessage2); // 結果 -> あけましておめでとうございます。今年もよろしくお願いします。 </code></pre> <p>「読点がなければ付け足す」という処理をメソッドに抽出したことで流用できるようになった。</p> <p>DRYはシステム全体での重複を(排除すべきところでは)排除しようという考え方、OAOOはあくまでソースコード上において、同じような内容はまとめようという考え方だと理解している。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16572 2021-01-09T22:31:27+09:00 2021-06-03T20:45:52+09:00 https://crieit.net/posts/csharp-naming 【C#】命名って大事だね <p>命名って大事だよね、ということはコードを書く人であればだいたい同意していただけると思う。</p> <p>というかコードを書かない人も「memo.txt」と「〇月〇日までに買う予定リスト.txt」だったら、ファイルの内容が同じでも後者の方が圧倒的にわかりやすいとは思っていただけると思う。</p> <p>毎度毎度弊社の困ったちゃんのことを書くのは忍びないが、だけど書くが、困ったちゃんははっきり言って命名がヘタクソである。私も別に命名に自信ニキという訳ではないが、まぁ困ったちゃんよりは上手く命名していると思うし、自分の命名で困ったこともあまりないので書いてみる。</p> <p>以降、サンプルコードにC#を用いるが別にどの言語でも共通する話だと思う。難しいことは書かないのでC#がわからなくてもだいたい読めると思う。</p> <h1 id="なぜ命名が大事か"><a href="#%E3%81%AA%E3%81%9C%E5%91%BD%E5%90%8D%E3%81%8C%E5%A4%A7%E4%BA%8B%E3%81%8B">なぜ命名が大事か</a></h1> <p>簡潔に言うと情報量の違いだと思う。</p> <p>極端な例だが、次のような変数宣言があるとする。</p> <pre><code class="csharp">decimal price = 100; </code></pre> <p><code>price</code>は価格・値段といった意味だが、はっきり言って<code>price</code>という文字だけでこれが何に使われるのか正確にはわからない。</p> <p>具体的には以下のような情報が欠けている。</p> <ul> <li>単価?小計?合計?</li> <li>税込み?税抜き?</li> <li>(キャンペーンなどがある場合)割引適用前?適用後?</li> <li>単位は日本円?ドル?</li> </ul> <p>こういった情報はできるだけ変数名に明示的に盛り込むべきだと考える。</p> <pre><code class="csharp">// 単価なら decimal unitPrice = 100; // 小計なら decimal subTotal = 100; // 合計なら decimal total = 100; // 税込みなら decimal taxIncludedPrice = 100; // 税抜きなら decimal taxExcludedPrice = 100; // 割引適用前なら decimal undiscountedPrice = 100; // または originalPrice // 割引適用後なら decimal discountedPrice = 100; // 単位が縁 decimal price_yen = 100; // 単位がドル decimal price_dol = 100; </code></pre> <p>といった具合に変数名に情報量を持たせることで可読性を上げることができる。</p> <p>この考え方は一部、アプリケーションハンガリアンといわれる考え方と一致する。</p> <p>たとえば、</p> <pre><code class="csharp">decimal total = price_yen + price_dol; </code></pre> <p>のようなコードはドルと日本円を加算しており、明らかに間違いだということが<strong>見ただけで</strong>分る。正しくは</p> <pre><code class="csharp">// ドルは円に換算して可算する decimal total = price_yen + ConvertToYen(price_dol); </code></pre> <p>としなければならないと判断できる。これが命名が持つ強力なメリットであり、命名にこだわる理由である。</p> <p>こういった考え方が活きる場面はたくさんある。</p> <pre><code class="csharp">// パスワード string password = "XXXXX"; // パスワードを更新 user.updatePassword(password); </code></pre> <p>これだと、<code>password</code>で更新しているが、うっかり<code>password</code>が平文のままだったら平文がそのまま登録されているかもしれない。</p> <p>もし変数名を<code>plainPassword</code>などとしていれば平文のパスワードということが見ただけで分かるので、登録前にハッシュ化させる必要があると気づけるだろう。</p> <pre><code class="csharp">// 平文のパスワード string plainPassword = "XXXXX"; // ハッシュ string hashedPassword = plainPassword.Hash(); // ハッシュ化されたパスワードで更新 user.updatePassword(hashedPassword); </code></pre> <p>また、コーディングミスにより</p> <pre><code class="csharp">// 誤って平文で更新するコードになっている user.updatePassword(plainPassword); </code></pre> <p>と書いてしまっていたとしても、「平文で更新するっておかしいよね?」と気づくきっかけになるハズだ。</p> <p>このような「これは税計算必要なの?」「これは平文?」などの情報はよくコードを読めば分かることではある。だがその「よくコードを読む」作業を意識から切り離して、本来考えたいアルゴリズムや可読性などに集中できるのが大事だ。そしてそれは命名の工夫をするだけで改善できることでもある。</p> <p>汎用性の高い接頭辞や接尾辞もあるので、それらはいつでも使えるようになっておくとベストだ。</p> <h1 id="xxxFlgという変数名"><a href="#xxxFlg%E3%81%A8%E3%81%84%E3%81%86%E5%A4%89%E6%95%B0%E5%90%8D">xxxFlgという変数名</a></h1> <p>真偽値などで<code>xxxFlg</code>などという命名をしているのをたまに見かけるが、これもあまり優しくないと思う。</p> <p>例えば<code>deleteFlg</code>という変数名があるとする。これは以下のように様々な解釈が可能である。</p> <ul> <li>削除済みのデータであることを示すフラグ</li> <li>削除できる状態のデータであることを示すフラグ</li> <li>削除しなければならないデータを示すフラグ</li> <li>削除される予定のデータであることを示すフラグ</li> </ul> <p>などだ。<code>delete</code>としか書かれていないのだから如何様にも解釈できる。<code>true</code>で削除済みなのか<code>false</code>で削除済みなのかも、変数名からは分からない。個人開発ならともかく、チームで開発するプロジェクトにおいて「そんなつもりで命名した訳じゃない」「普通に考えたらこう」なんていうのは通用しない。何故ならそのコードは自分だけではなく、他の誰かが読んだり修正したりするかもしれないからだ。個人開発だとしても、何日も経って忘れたころに見直すこともあるだろう。だからこそ、直感的に分かりやすい命名をする必要がある。</p> <p>真偽値の命名には<code>is</code>, <code>has</code>, <code>can</code>(もしくは<code>is~able</code>), <code>should</code>などを使うと可読性が上がると言われている。それらはY/N質問として読めるし、意味のはき違えようがないからだ。</p> <pre><code class="csharp">// 削除済み bool isDeleted = true; // 削除できる bool canDelete = true; bool isDeletable = true; // 削除しなければならない bool shouldDelete = true; </code></pre> <p>これだと、<code>isDeleted</code>は「削除済みか?」に対して<code>true</code>(削除済み)か<code>false</code>(削除済みではない)とハッキリするのである。<code>isDeleted</code>という変数名で「<code>true</code>だから『削除しなければならない』ってことだな!」「<code>true</code>だから未削除なんだな!」と解釈する人はまずいないだろう。それは考え方とかじゃなくて英語力の問題だから…。</p> <h1 id="コメントで補えるのでは"><a href="#%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%81%A7%E8%A3%9C%E3%81%88%E3%82%8B%E3%81%AE%E3%81%A7%E3%81%AF">コメントで補えるのでは</a></h1> <p>命名をシンプルにしてコメントで補えるのでは、と思うかもしれない。確かにそういう手もあるが、個人的には望ましくないと考える。なぜか。</p> <p>それはコメントはメンテしないといけないからだ。</p> <pre><code class="csharp">// 税抜き価格 decimal price = 100; </code></pre> <p>というコメントをつけたコードがあったとする。これが税込み価格を反映するという修正が必要になった場合、</p> <pre><code class="csharp">// 税抜き価格 decimal price = 110; </code></pre> <p>と修正するだけでなく、コメントも</p> <pre><code class="csharp">// 税込み価格 decimal price = 110; </code></pre> <p>と直さなければならなくなる。修正の工数がしっかり確保されており、コメントも含めてレビューされるような環境であればまだいいかもしれないが、多くの場合修正レビューはテストが通るかどうかと差分の比較であり、コメントは忘れ去られる。動作は自動テストが保障してくれるかもしれないが、コメントが合っているかどうかは人の目でみないと分からない。</p> <p>もしコメントのメンテを怠ると、そのコメントは嘘になる。コメントに嘘が書いてあるくらいなら、コメントがない方が100倍ましだ。そうやって嘘のコメントが積み重なって保守不可能な過去の遺産になっていく。コメントで意味を補足するくらいなら、<code>taxExcludedPrice</code>や<code>taxIncludedPrice</code>などの具体的な意味を持った命名をした方が良い。</p> <h1 id="システムハンガリアンは…"><a href="#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%83%8F%E3%83%B3%E3%82%AC%E3%83%AA%E3%82%A2%E3%83%B3%E3%81%AF%E2%80%A6">システムハンガリアンは…</a></h1> <p>前述した「アプリケーションハンガリアン」とは別に「システムハンガリアン」という考え方もあるが、これは現代ではもはや推奨されていないシーンの方が多い。以前は私も使っていたが、最近はまず使わないようになった。</p> <pre><code class="csharp">string sName = "あぱしょに"; int nAge = 27; </code></pre> <p>システムハンガリアンは、<code>string</code>型なら<strong>s</strong>や<strong>str</strong>、int型なら<strong>n</strong>や<strong>int</strong>のように、各変数の型を識別できる接頭辞を入れるというものだ。</p> <p>これも同様にコードを見ただけでエラーが分かるようになるというハックの一つだが、静的型付け言語ではたとえば</p> <pre><code class="csharp">string sName = "あぱしょに"; int nAge = 27; int hoge = nAge * sName; // エラー </code></pre> <p>などと間違えて書いても<br /> <a href="https://crieit.now.sh/upload_images/9efd38256758a1d84d90e3d5578cfd715ff9b00d04cd3.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9efd38256758a1d84d90e3d5578cfd715ff9b00d04cd3.jpg?mw=700" alt="image" /></a><br /> のように教えてくれるので、変数を見ただけで型の不一致がわかるというメリットは少ない。動的型付け言語であれば違うと思うが…。</p> <p>また、システムハンガリアンは型を変更した際に変数名も修正しなければならない(そうでないとシステムハンガリアンのメリットが正しく発揮されない)。たとえば<code>nUserId</code>を<code>int</code>型から<code>string</code>型に変更した場合は、型だけでなく変数名も<code>sUserId</code>と変更しなければならず、これは大変な手間である。</p> <p>そういった理由で、システムハンガリアンは現代ではほとんど見られなくなった(少なくとも私の観測範囲では)。これからも私が積極的に使うことはないだろう。昔の案件の修正などで既にコーディングスタイルが指定されていれば従うが。</p> <h1 id="英語にこだわらなくていい"><a href="#%E8%8B%B1%E8%AA%9E%E3%81%AB%E3%81%93%E3%81%A0%E3%82%8F%E3%82%89%E3%81%AA%E3%81%8F%E3%81%A6%E3%81%84%E3%81%84">英語にこだわらなくていい</a></h1> <p>個人的には無理して英語にこだわって意味不明な変数名になるよりは、別にローマ字でもいいから意味が分かるよう正確に命名してほしいと思っている。</p> <p>また、英語に自信がなければ<a target="_blank" rel="nofollow noopener" href="https://codic.jp/">codic</a>や<a target="_blank" rel="nofollow noopener" href="https://www.deepl.com/translator">DeepL</a>など便利なサービスもあるので、それらも活用したい。特に前者はネーミングに特化しているだけでなく、<code>camelCase</code>や<code>snake_case</code>、<code>PascalCase</code>など自由にスタイルをカスタマイズできる。私はこれらのサービスで色んな語を知った。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16567 2021-01-08T21:12:03+09:00 2021-01-08T21:12:03+09:00 https://crieit.net/posts/csharp-expression-bodied 【C#】ワンライナーで書ける式形式メンバー <h1 id="式形式メンバー"><a href="#%E5%BC%8F%E5%BD%A2%E5%BC%8F%E3%83%A1%E3%83%B3%E3%83%90%E3%83%BC">式形式メンバー</a></h1> <p>まずは下記のコードをご覧ください。</p> <pre><code class="csharp">public static bool IsNullOrEmpty(this string text) { return string.IsNullOrEmpty(text); } </code></pre> <p>上記は<a target="_blank" rel="nofollow noopener" href="/2020/12/30/csharp-extension-methods/">【C#】拡張メソッド</a>という記事で例示した、<code>string.IsNullOrEmpty()</code>を扱いやすくした拡張メソッドです。</p> <pre><code class="csharp">var text = ""; // これが Cnosole.Write(string.IsNullOrEmpty(text)); // こう書ける Console.Write(text.IsNullOrEmpty()); </code></pre> <p>という方法を実現する拡張メソッドですね。</p> <p>このコード、<code>string.IsNullOrEmpty()</code>を<code>return</code>しているだけなのに<code>{}</code>で括ってあげるのがなんか嫌じゃないですか?</p> <p>だって、<code>foreach</code>も<code>if</code>も、続く処理が単文(単一ステートメント)であれば<code>{}</code>は不要ですよね。</p> <pre><code class="csharp">// これは foreach (var n in Enumerable.Range(1, 20)) { Console.Write(n); } // こう書ける foreach (var n in Enumerable.Range(1, 20)) Console.Write(n); // これは if ((n == 3) || ($"{n}".Contains(3))) { Console.Write("アホ"); } // こう書ける if ((n == 3) || ($"{n}".Contains(3))) Console.Write("アホ"); </code></pre> <p>組み合わせて</p> <pre><code class="csharp">// これを foreach (var n in Enumerable.Range(1, 20)) { if ((n == 3) || ($"{n}".Contains(3))) { Console.Write("アホ"); } } // こう書ける foreach (var n in Enumerable.Range(1, 20)) if ((n == 3) || ($"{n}".Contains(3))) Console.Write("アホ"); </code></pre> <p>こうも書ける訳です。余計な<code>{}</code>がなくてスッキリしていますよね。特に先人の残した<strong>ネスト地獄</strong>のコードを読んでいると、<code>{}</code>なんて見るのも嫌じゃないですか。</p> <p>だから、メソッドでも<code>{}</code>なしで書きたいですよね?ですよね?</p> <p>メソッドも、<code>{}</code>を省略して書けるケースがあります。</p> <pre><code class="csharp">// これは public static bool IsNullOrEmpty(this string text) { return string.IsNullOrEmpty(text); } // こう書ける public static bool IsNullOrEmpty(this string text) => string.IsNullOrEmpty(text); </code></pre> <p>どうでしょうか?<code>{}</code>が減っただけですが、かなりスッキリしましたよね。</p> <p>こういう記法ができる式形式メンバーは<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-version-history">C#6.0で実装され、C#7.0で拡張されました。</a></p> <h1 id="条件"><a href="#%E6%9D%A1%E4%BB%B6">条件</a></h1> <p>式形式メンバーは、単一ステートメントで記載されているメソッドやプロパティに使用できます。</p> <p>なので、たとえば以下のようなメソッドは、式形式メンバーは使用できません。</p> <pre><code class="csharp">// アホ毛・パーカー・ショートヘア・ニーソのいずれか2つ以上を満たせばボーイッシュとする public static bool IsBoyish(this GirlModel girl) { var boyishibility = 0; // アホ毛か if (girl.HasAhoge) boyishibility++; // パーカーか if (girl.IsWearingHoodie) boyishibility++; // ショートヘアか if (girl.HasShortHair) boyishibility++; // ニーソか if (girl.IsWearingKneeSocks) boyishibility++; // 判定 return (boyishibility >= 2); } </code></pre> <p>ですが、たとえばボーイッシュがより広く浸透した世界で、アホ毛・パーカー・ショートヘア・ニーソのいずれか一つでも満たせばボーイッシュといえるようになった場合は、下記のように書けます。</p> <pre><code class="csharp">public static bool IsBoyish(this GirlModel girl) => (girl.HasAhoge || girl.IsWearingHoodie || girl.HasShortHair || girl.IsWearingKneeSocks); </code></pre> <p>ちなみにアホ毛・パーカー・ショートヘア・ニーソは私のHN「あぱしょに」の<a target="_blank" rel="nofollow noopener" href="/2020/12/01/renamed/">名前の由来</a>です(小声)</p> <h1 id="書き方"><a href="#%E6%9B%B8%E3%81%8D%E6%96%B9">書き方</a></h1> <p>書き方は、<code>{}</code>の代わりに<code>=></code>を使用して、<code>return</code>を省略するという記法になります。</p> <pre><code class="csharp">// これは× // returnは書かない public static bool IsNullOrEmpty(this string text) => return string.IsNullOrEmpty(text); </code></pre> <p>この記法は、続くステートメントを<code>return</code>しますよ、ということをコンパイラに通知しているんですね。なので明示的に<code>return</code>は書けません。</p> <p><code>{}</code>を省略<strong>できる</strong>と書きましたがどちらかというと副産物的なメリットで、本来は<code>return</code>を省略するための記法ということになります。</p> <h1 id="戻り値を返さないメソッドにも使える"><a href="#%E6%88%BB%E3%82%8A%E5%80%A4%E3%82%92%E8%BF%94%E3%81%95%E3%81%AA%E3%81%84%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E3%82%82%E4%BD%BF%E3%81%88%E3%82%8B">戻り値を返さないメソッドにも使える</a></h1> <p>戻り値を返さないメソッドに対しても、単一ステートメントのメソッドであれば式形式メンバーを使用できます。</p> <pre><code class="csharp">// これは public static void WriteToConsole(this object logMessage) { Console.Write(logMessage); } // こう書ける public static void WriteToConsole(this object logMessage) => Console.Write(logMessage); </code></pre> <p>これらのメソッドはもともと、ふつうは<code>return</code>を省略するかと思いますが式形式メンバーでも同様に<code>return</code>は書きません。</p> <h1 id="式形式メンバーは便利か"><a href="#%E5%BC%8F%E5%BD%A2%E5%BC%8F%E3%83%A1%E3%83%B3%E3%83%90%E3%83%BC%E3%81%AF%E4%BE%BF%E5%88%A9%E3%81%8B">式形式メンバーは便利か</a></h1> <p>個人的には、使えるところでは積極的に使っています。タイプ量が減るのも良いですが、ワンライナーで書けるのがやっぱり楽です。慣れてしまえば読む時でもメリットがあると思います。</p> <p>ただし、実際の処理が改行&インデントされていれば、それはそれで読みやすいということもあると思います。その辺りは個人の感覚によりけりですが、こういう記法があるということを知っているだけでも訳に立つと思います。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-version-history">C# の歴史</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members">式形式のメンバー (C# プログラミング ガイド)</a></p> あぱしょに tag:crieit.net,2005:PublicArticle/16547 2021-01-06T20:08:06+09:00 2021-01-06T20:09:22+09:00 https://crieit.net/posts/csharp-var 【C#】varの使い方、メリットとデメリット <h1 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h1> <p>C#では変数宣言時、型を書く必要がある。</p> <pre><code class="csharp">string text = "あいうえお"; </code></pre> <p>この時、<code>var</code>キーワードを使用することで右辺から型を推論させて変数宣言を行える。</p> <pre><code class="csharp">var text = "あいうえお"; // textはstring型となる </code></pre> <p>Visual Studioで確認すると、<code>string</code>型となっていることがわかる。<br /> <a href="https://crieit.now.sh/upload_images/e84f7ee882c4fb25f5501a6ff08accd65ff59a5ca7382.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e84f7ee882c4fb25f5501a6ff08accd65ff59a5ca7382.jpg?mw=700" alt="image" /></a></p> <p><code>var</code>は右辺から型を判別している。そのため、下記のコードはコンパイルエラーとなる。</p> <pre><code class="csharp">var text = null; // nullの型を判別できないためコンパイルエラーとなる </code></pre> <p>もし<code>string</code>型の<code>null</code>を<code>var</code>を使って宣言する場合、下記のように書かなければならない。</p> <pre><code class="csharp">var text = (string)null; </code></pre> <p>この記法だと一応宣言はできるが、あまり型推論のメリットがない。(後述するようにメリットがない訳ではない)</p> <h1 id="メリット"><a href="#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">メリット</a></h1> <p><code>var</code>を使うと次のメリットがある。</p> <ul> <li>複雑な型名を書かなくても良い</li> <li>型が変更されたときに書き直さなくても良い</li> <li>コードが読みやすくなる</li> </ul> <p>順番に解説する。</p> <h2 id="複雑な型名を書かなくても良い"><a href="#%E8%A4%87%E9%9B%91%E3%81%AA%E5%9E%8B%E5%90%8D%E3%82%92%E6%9B%B8%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%A6%E3%82%82%E8%89%AF%E3%81%84">複雑な型名を書かなくても良い</a></h2> <p>たとえば<code>Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>></code>を返す<code>NegativeMethod</code>という関数があるとしよう。</p> <p>これも<code>var</code>を使うと型名を書かなくてよい。</p> <pre><code class="csharp">public static Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>> NegativeMethod() { Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>> something = new Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>>(); // ~複雑な処理 return something; } Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>> result = NegativeMethod(); </code></pre> <p>上記のようなコードが、下記のように書ける。</p> <pre><code class="csharp">public static Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>> NegativeMethod() { var something = new Dictionary<Dictionary<Dictionary<int, string>, string>, Dictionary<Dictionary<int, string>, Dictionary<int, string>>>(); // ~複雑な処理 return something; } var result = NegativeMethod(); </code></pre> <p>型が明確になっている場合は、複雑な型を書かなくて良くなる。<code>Tuple</code>や<code>Dictionary</code>は複雑になる場合もあるが、それをいちいち書かなくても良いメリットとなる。なお、後述するがこういう使い方にはデメリットもあるので注意。</p> <h2 id="型が変更されたときに書き直さなくても良い"><a href="#%E5%9E%8B%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E6%9B%B8%E3%81%8D%E7%9B%B4%E3%81%95%E3%81%AA%E3%81%8F%E3%81%A6%E3%82%82%E8%89%AF%E3%81%84">型が変更されたときに書き直さなくても良い</a></h2> <p>たとえば<code>userId</code>は<code>int</code>型だったが、アルファベットを含めるという仕様変更にあたり<code>string</code>型に変更することになったとする。</p> <p>ここで、下記のように修正する必要がある。</p> <pre><code class="csharp">// GenerateUserIdはintを返す int userId = GenerateUserId(); // ▲これを下記のように変更する▼ // GenerateUserIdはstringを返すようになった string userId = GenerateUserId(); </code></pre> <p>数か所であれば問題ないがもし何十箇所・何百箇所も記載してあればそれらを漏れなく修正する必要がある。</p> <p>一括で置換するという方法もない訳ではないが、対象が漏れなく置換されたか、本来修正範囲外のコードが変更されていないかなどの差分確認の作業が結局発生する。</p> <p>初めから<code>var</code>で記載しておけば型が変更されてもコードの修正が必要なく、修正後にテストを走らせればそれだけで済む。</p> <pre><code class="csharp">// GenerateUserIdがintからstringになっても、ここは修正する必要がない var userId = GenerateUserId(); </code></pre> <h2 id="コードが読みやすくなる"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8C%E8%AA%AD%E3%81%BF%E3%82%84%E3%81%99%E3%81%8F%E3%81%AA%E3%82%8B">コードが読みやすくなる</a></h2> <p>ここは感覚的な問題だが例を示す。</p> <p>下記のようなコードがあるとする。</p> <pre><code class="csharp">int userId = GenerateUserId(); string firstName = ""; string lastName = ""; DateTime birthday = new DateTime(1990, 1, 1); </code></pre> <p>これは<code>var</code>を使用すると次のように書ける。</p> <pre><code class="csharp">var userId = GenerateUserId(); var firstName = ""; var lastName = ""; var birthday = new DateTime(1990, 1, 1); </code></pre> <p>こうすると型の部分が全て<code>var</code>になり、変数名のインデントが揃って読みやすくなる…気がする。少なくとも私は、バラバラに型を書いているより<code>var</code>で揃っていた方が読みやすい。</p> <h1 id="デメリット"><a href="#%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">デメリット</a></h1> <p>上記のようにとてもメリットの大きな<code>var</code>だが、デメリットとなるケースも存在する。</p> <p>それは、人間が容易に型を推論できない場合だ。</p> <pre><code class="csharp">var firstName = GetFirstName(userId); </code></pre> <p>上記の場合、おそらく<code>firstName</code>は<code>string</code>型になるだろうという想像が出来る。<code>var</code>を使用するのが相応しいケースの一つだ。</p> <p>しかし下記の場合はどうだろう?</p> <pre><code class="csharp">var idAndNames = GetIdAndNames(); </code></pre> <p><code>GetIdAndNames</code>はおそらく<code>id</code>と<code>name</code>を(おそらくペアで)取得するメソッドだと予想はできるが、どういう型になっているかは実装部分を見ないと判定できない。<code>Dictionary<int, string></code>かもしれないし<code>List<Tuple<int, string>></code>かもしれない。独自クラスのコレクションになっている可能性もある。</p> <p><code>var</code>を使用したら<code>GetIdAndNames</code>がどのような型を返すとしても寛容に受け入れてしまう反面、型が分かりにくくなるというデメリットもある。(上記の例は関数名などのネーミングの問題もあるが)</p> <p>Visual StudioなどのIDE上で読むのであれば、変数名にマウスオーバーしたら型が分かるが、バージョン管理ソフトの履歴などの画面では当然そのような機能がないため読みづらい。型が分かりにくい場合は型を明記するが、コメントを添えておくと良いだろう。</p> <p>コメントは後で型が変わった時などにメンテしなければならないので、明記する方が個人的には良いと思う。型を明記していればコンパイルエラーなどで検知できるので修正漏れを防ぐ助けにもなる。</p> <pre><code class="csharp">// varを使わず型を明記する場合 Dictionary<int, string> = GetIdAndNames(); // コメントを添える場合 // GetIdAndNamesはDictionary<int, string> var idAndNames = GetIdAndNames(); </code></pre> <h1><code>var</code>は使うべきか</h1> <p>これはもちろんチームのコーディング規約や方針に従わなければならないが、少なくとも私は個人開発では積極的に利用する。私がアサインされたチームでも今のところ「<code>var</code>は禁止」というところはなかったので非常に助かっている。</p> <p>Microsoftの<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/inside-a-program/coding-conventions">C# のコーディング規則 (C# プログラミング ガイド)</a>にも、</p> <blockquote> <p>変数の型が割り当ての右側から明らかである場合、または厳密な型が重要でない場合は、ローカル変数の暗黙の型指定を使用します。</p> </blockquote> <p>と明記されている。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16538 2021-01-05T20:44:40+09:00 2021-01-05T20:44:40+09:00 https://crieit.net/posts/csharp-extension-methods 【C#】拡張メソッド <h1 id="序論"><a href="#%E5%BA%8F%E8%AB%96">序論</a></h1> <p>C#には拡張メソッドという機能があって、まぁ知ってれば大変便利なのですが、弊社謹製レガシーライブラリはなんともまぁ前時代的な書き方をしてあり大変煩わしいしです。そのうえ誰もメンテしないから使いにくくて仕方がない、私がメンテしようにもテストコードすら書かれていないのでめんどくせぇ。というヘイト全開で拡張メソッドの素晴らしさを書いていこうと思います。弊社に届け、この思い☆</p> <h1 id="拡張メソッド"><a href="#%E6%8B%A1%E5%BC%B5%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89">拡張メソッド</a></h1> <p>C#における拡張メソッドは、<strong>あたかもそのクラスに元からあるかのように使用できるインスタンスメソッドを実装する</strong>という方法です。</p> <p>たとえば、stringには<code>string.IsNullOrEmpty</code>というメソッドがあります。引数で渡された文字列が空白またはnullの場合にtrueを返してくれるというメソッドです。</p> <pre><code class="csharp">string text = ""; Console.Write(string.IsNullOrEmpty(text)); // 結果 -> true </code></pre> <p>この書き方、なんか嫌じゃないですか?つまり、<code>text</code>というインスタンスが<code>IsNullOrEmpty</code>かどうかをいちいちstringのメソッド越しに聞くの嫌じゃないですか?子どもと話してて、お父さんお母さんに「何歳ですか?」って聞いてるみたいで嫌じゃないですか?子どもに聞いて子どもに答えてほしい。なのでそうしましょう。</p> <pre><code class="csharp">public static class StringExtension { public static bool IsNullOrEmpty(this string text) { return string.IsNullOrEmpty(text); } } </code></pre> <p>上記のメソッドを用意することで、以下のように書けます。</p> <pre><code class="csharp">string text = ""; Console.Write(text.IsNullOrEmpty()); // 結果 -> true </code></pre> <p>比べてみましょう。</p> <pre><code class="csharp">string text = ""; Console.Write(string.IsNullOrEmpty(text)); // 拡張メソッドじゃないver Console.Write(text.IsNullOrEmpty()); // 拡張メソッドver </code></pre> <p>あらまぁ見てくださいよこのコード量の違いを。素晴らしいですね。</p> <p>あたかも、<code>text</code>というインスタンスの<code>IsNullOrEmpty</code>メソッドを呼び出しているようでしょう。</p> <h1 id="拡張メソッド記述のポイント"><a href="#%E6%8B%A1%E5%BC%B5%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E8%A8%98%E8%BF%B0%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88">拡張メソッド記述のポイント</a></h1> <p>拡張メソッドのポイントは3つ。</p> <ol> <li>staticクラス内に記載すること</li> <li>staticメソッドにすること</li> <li>第1引数の頭に<code>this</code>キーワードをつけること</li> </ol> <p>こうすることで、そのメソッドをインスタンスメソッドのように扱うことができます。</p> <h1 id="コードがスッキリする"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8C%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%81%99%E3%82%8B">コードがスッキリする</a></h1> <p>例えば以下のようなコードがあるとしましょう。</p> <pre><code class="csharp">string result = string.Join(",", Enumerable.Range(1, 20).Select(n => ((n % 3 == 0) || $"{n}".Contain("3")) ? "アホ" : $"{n}")); // 結果 -> 1, 2, アホ, 4, 5, アホ, 7, 8, アホ, 10, 11, アホ, アホ, 14, アホ, 16, 17, アホ, 19, 20 </code></pre> <p>上記コードは1から20のうち、<strong>3の倍数と3がつく数字の時は"アホ"を、それ以外はその数字を返すコレクションを<code>", "</code>で区切って連結する</strong>というものですが、このコードを読む時って</p> <ol> <li>まず<code>string.Join</code>があることで何かが区切って連結される</li> <li>何が区切られるかを読む</li> </ol> <p>という順番で読むことになると思います。で、何が区切られるのかを読み始めたらめんどくさいコレクション操作をしていて嫌になりますね。</p> <p>ですが、以下のような拡張メソッドが用意されていたらどうでしょう。</p> <pre><code class="csharp">public static class GenericsExtensions { public static string JoinBy<T>(this IEnumerable<T> source, string separater) { // nullチェックは省略 return string.Join(separater, source); } } </code></pre> <p>これを使うと、以下のように書けます。</p> <pre><code class="csharp">string result = Enumerable.Range(1, 20).Select(n => ((n % 3 == 0) || $"{n}".Contain("3")) ? "アホ" : $"{n}").JoinBy(","); // 結果 -> 1, 2, アホ, 4, 5, アホ, 7, 8, アホ, 10, 11, アホ, アホ, 14, アホ, 16, 17, アホ, 19, 20 </code></pre> <p>これならどうでしょう。対象の数字を"アホ"へ変換した後に、<code>", "</code>で区切って連結させると<strong>自然に</strong>読めますよね。</p> <p>この自然に読めるということが超重要なんです。ただでさえ頭を使うことが多いので、不要なストレスを省くようにすべきだと思います。(別の話になりますが、LINQは頭の中のロジック通りに書けて、書いてある通りに自然に読めるので本当にすごい)</p> <p>拡張メソッドを上手く活用できれば、可読性を上げることもできます。</p> <p>おまけですが、以下のようなアホ判定拡張メソッドを用意するともっとスッキリすることでしょう。</p> <pre><code class="csharp">public static class IntExtensions { public static bool IsMultipleOf(this int value, int @base) { return (value % @base == 0); } public static bool IsAho(this int value) { const int AhoTrigger = 3; return value.IsMultipleOf(AhoTrigger) || $"{value}".Contains($"{AhoTrigger}"); } } // こう書ける string result = Enumerable.Range(1, 20).Select(n => n.IsAho() ? "アホ" : $"{n}").JoinBy(", "); // 結果 -> 1, 2, アホ, 4, 5, アホ, 7, 8, アホ, 10, 11, アホ, アホ, 14, アホ, 16, 17, アホ, 19, 20 </code></pre> <h1 id="終わりに"><a href="#%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%AB">終わりに</a></h1> <p>拡張メソッドは上記の通りとてもメリットが大きく、一度実装してしまえば使う側は何も考えずに使える素晴らしい機能です。再利用性や拡張性も高いです。</p> <p>とりあえずよく使う<code>string.IsNullOrEmpty</code>や<code>string.Format</code>などを拡張メソッドにするだけでもかなり便利になります。</p> <p>これでみんな幸せになります、めでたしめでたし…と言いたいところですが全然めでたくないのが弊社です。私は日々弊社謹製レガシーライブラリと戦っています。ちくせう。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16503 2021-01-04T21:06:50+09:00 2021-01-04T21:09:12+09:00 https://crieit.net/posts/C-string-interpolation 【C#】string-interpolation(文字列補間)の使い方とメリット <h1 id="序論"><a href="#%E5%BA%8F%E8%AB%96">序論</a></h1> <p>なんか最近仕事してて、自分が当たり前と思っていることでも、そうじゃない人もいるんだなぁと改めて思った。これは考えてみれば当然のことではあるんだけど。</p> <p>先日C#のコードを書いていたら、それを見た人から「これ何?」と聞かれた。C#の<code>string-interpolation</code>(文字列補間)の書式が分からないらしかった。私は<code>string.Format</code>よりも文字列補間式を好むのだが、まぁ分からなければ難しいのだろうか?学習コストはそこまで高くないと個人的には思うし、こういうことがあるのでせっかくならまとめてみようと思った。</p> <p>まぁそんな固く考えなくてもとりあえず書いちゃえって感じなので書きます。ついでに<code>string.Format</code>についてもおさらい。</p> <h1 id="string.Format"><a href="#string.Format">string.Format</a></h1> <p>string型には<code>Format</code>というメソッドがある。これは文字列の中に<code>{0}</code>などのプレースホルダを設定しておくと、引数をそこに入れてくれるというものだ。</p> <pre><code class="csharp">const string name = "あぱしょに"; const int age = 27; string text = "私の名前は{0}です。年齢は{1}歳です。"; string formattedText = string.Format(text, name, age); // 結果 -> 私の名前はあぱしょにです。年齢は27歳です。 </code></pre> <p>ちなみにパラメータ引数はobject型を取って内部で<code>ToString</code>しているので型は意識しなくても良い。params object[]を取るので型がバラバラでも構わない。</p> <p>プレースホルダは同じインデックスを指定するとまとめて入れることもできる。</p> <pre><code class="csharp">const string favoriteName = "リンゴ"; const string favoritePoint = "甘い"; string text = "私の好物は{0}です。{0}の好きなところは、{1}ところです。"; string formattedText = string.Format(text, favoriteName, favoritePoint); // 結果 -> 私の好物はリンゴです。リンゴの好きなところは、甘いところです。 </code></pre> <p>第2パラメータ以降で必要な数は、<strong>プレースホルダの数ではなくプレースホルダの種類(割り当てたインデックス)の数</strong>である。プレースホルダが2種類で合計3個なら、第2パラメータ以降は2つ指定する。</p> <p>書式設定を指定することも出来る。プレースホルダのインデックスの後に<code>:</code>を挟んで書式設定を指定する。</p> <pre><code class="csharp">DateTime Nov042020 = new DateTime(2020, 11, 4); string text = "今日は{0:yyyy年MM月dd日}です。"; string formattedText = string.Format(text, Nov042020); // 結果 -> 今日は2020年11月04日です。 </code></pre> <h1 id="拡張メソッド化"><a href="#%E6%8B%A1%E5%BC%B5%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E5%8C%96">拡張メソッド化</a></h1> <p>string.Formatをいちいち書くのは面倒なので、拡張メソッドを用意している人も多いと思う。</p> <pre><code class="csharp">public static class StringExtension { public static string FormatEx(this string text, params object[] parameters) => string.Format(text, parameters); } </code></pre> <p>たとえば上記のようなメソッドを用意しておけば、</p> <pre><code class="csharp">DateTime Nov042020 = new DateTime(2020, 11, 4); string text = "今日は{0:yyyy年MM月dd日}です。"; string formattedText = text.FormatEx(Nov042020); // 結果 -> 今日は2020年11月04日です。 </code></pre> <p>のように使える。こういう拡張メソッドを用意しておくとタイプ量も減るしテストもしやすいし可読性も上がると個人的には思う。</p> <h1 id="string.Formatのイマイチな点"><a href="#string.Format%E3%81%AE%E3%82%A4%E3%83%9E%E3%82%A4%E3%83%81%E3%81%AA%E7%82%B9">string.Formatのイマイチな点</a></h1> <p>2点ある。</p> <ol> <li>パラメータがどのプレースホルダに入るのか分かりづらい</li> <li>コンパイルエラーを検出してくれない</li> </ol> <p>まずは1点目。プレースホルダが少なければ別に問題ないのだが、たとえば</p> <pre><code class="csharp">const string LangName = "C#"; const string LangName_ja = "シーシャープ"; const string DevelopersFirstName = "アンダース"; const string DevelopersLastName = "ヘルスバーグ"; const string ProgrammingLang = "プログラミング言語"; const string LangsGroupOfC = "C系言語"; const string SampleLang1 = "C言語"; const string SampleLang2 = "C++"; const string SampleLang3 = "Java"; const string DevelopersOffice = "ボーランド"; const string SampleLang4 = "Delphi"; string text = "{0}({1})は、{2}・{3}が設計した{4}であり、構文はその名前にもある通り{5}({6}、{7}や{8}など)の影響があるが、構文以外の言語機能などについては{3}が以前の所属である{9}で設計した{10}からの影響がある。"; string formattedText = string.Format(text, LangName, LangName_ja, DevelopersFirstName, DevelopersLastName, ProgrammingLang, LangsGroupOfC, SampleLang1, SampleLang2, SampleLang3, DevelopersOffice, SampleLang4); // 結果 -> C#(シーシャープ)は、アンダース・ヘルスバーグが設計したプログラミング言語であり、構文はその名前にもある通りC系言語(C言語、C++やJavaなど)の影響があるが、構文以外の言語機能などについてはヘルスバーグが以前の所属であるボーランドで設計したDelphiからの影響がある。 </code></pre> <p>上記のような長ったらしい文字列の補間などは目で追う気にもならない。はっきりと言って苦痛である。(例文は<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/C_Sharp">C#のwikipediaのページ</a>より拝借した)</p> <p>こういうコードを書く方が問題だといえば当然そうなのだが、しかし現実問題として先人が書いたこういうコードを読むことが何度もあった。</p> <p>こういうふうにプレースホルダとパラメータが多いと、対応するパラメータを探すのだけで一苦労であるし、上記では<code>{3}</code>が2回登場しているが、その度にパラメータをまた辿りなおすのも面倒である。</p> <p>整形後のイメージがコメントで書いてあればまだマシだが、そこまで気が利くような人はこんな長ったらしい<code>Format</code>を書かないのである。</p> <p>そして2点目、<code>string.Format</code>はコンパイルエラーを検知できない。</p> <pre><code class="csharp">const string favoriteName = "リンゴ"; const string favoritePoint = "甘い"; string text = "{0}の好きなところは、{1}ところです。"; string formattedText = string.Format(text, favoriteName); // パラメータに favoritePoint を指定するのを忘れている // 結果 -> 実行時エラー </code></pre> <p>上記のコードはプレースホルダに対してパラメータが不足しているため、下記の実行時エラーが発生する。</p> <blockquote> <p>System.FormatException: 'インデックス (0 ベース) は 0 以上で、引数リストのサイズよりも小さくなければなりません。'</p> </blockquote> <p>しかし、<code>string.Format</code>の構文としては正しい(第1引数がstring、第二引数がobject)ため、コンパイルエラーとして検知されない。つまり、実行してみるまでエラーになるか分らないのだ。</p> <p>上記のような簡単なコードであれば原因も分かるが、「1.パラメータがどのプレースホルダに入るのか分かりづらい」の説明に例示したような複雑に入り組んだコードの場合は原因を探すモチベーションすら湧いてこないだろう。</p> <p>上記の問題は、いずれも<code>string-interpolation</code>(文字列補間)を利用することで解消できる。</p> <h1 id="string-interpolation(文字列補間)"><a href="#string-interpolation%28%E6%96%87%E5%AD%97%E5%88%97%E8%A3%9C%E9%96%93%29">string-interpolation(文字列補間)</a></h1> <p>この機能は<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-version-history">C# 6.0から利用できるようになっている</a>。</p> <p>利用するには、文字列の最初に<code>$</code>を付けて、プレースホルダは<code>{}</code>で囲んだ中に直接値(あるいは変数・定数など)を書き込む。</p> <pre><code class="csharp">const string name = "あぱしょに"; const int age = 27; string formattedText = $"私の名前は{name}です。年齢は{age}歳です。"; // 結果 -> 私の名前はあぱしょにです。年齢は27歳です。 </code></pre> <p>書式設定を指定する時は、値の後に<code>:</code>を挟み指定することができる。</p> <pre><code class="csharp">DateTime Nov042020 = new DateTime(2020, 11, 4); string formattedText = $"今日は{Nov042020:yyyy年MM月dd日}です。"; // 結果 -> 今日は2020年11月04日です。 </code></pre> <p>という具合だ。</p> <p>また、プレースホルダが多い場合でも</p> <pre><code class="csharp">const string LangName = "C#"; const string LangName_ja = "シーシャープ"; const string DevelopersFirstName = "アンダース"; const string DevelopersLastName = "ヘルスバーグ"; const string ProgrammingLang = "プログラミング言語"; const string LangsGroupOfC = "C系言語"; const string SampleLang1 = "C言語"; const string SampleLang2 = "C++"; const string SampleLang3 = "Java"; const string DevelopersOffice = "ボーランド"; const string SampleLang4 = "Delphi"; string formattedText = $"{LangName}({LangName_ja})は、{DevelopersFirstName}・{DevelopersLastNameが設計した{ProgrammingLang<span>}</span><span>}</span>であり、構文はその名前にもある通り{LangsGroupOfC}({SampleLang1}、{SampleLang2}や{SampleLang3}など)の影響があるが、構文以外の言語機能などについては{DevelopersLastName}が以前の所属である{DevelopersOffice}で設計した{SampleLang4}からの影響がある。"; // 結果 -> C#(シーシャープ)は、アンダース・ヘルスバーグが設計したプログラミング言語であり、構文はその名前にもある通りC系言語(C言語、C++やJavaなど)の影響があるが、構文以外の言語機能などについてはヘルスバーグが以前の所属であるボーランドで設計したDelphiからの影響がある。 </code></pre> <p>のように位置と値をセットで記述できるので、<code>string.Format</code>に比べて読みやすいと思う。</p> <p>プレースホルダに対して値を設定していない場合はコンパイルエラーとして検出してくれるので便利だ。</p> <pre><code class="csharp">const string favoriteName = "リンゴ"; const string favoritePoint = "甘い"; string formattedText = $"{favoriteName}の好きなところは、{}ところです。"; // パラメータに favoritePoint を指定するのを忘れている // 結果 -> コンパイルエラー </code></pre> <blockquote> <p>CS1733:式が必要です。</p> </blockquote> <h1 id="余談"><a href="#%E4%BD%99%E8%AB%87">余談</a></h1> <p>文字列補間とはあまり関係ない余談だが、先日新人さんが</p> <pre><code class="csharp">DateTime Nov042020 = new DateTime(2020, 11, 4); string formattedText = Nov042020.ToString("今日はyyyy/M/dです。"); // 結果 -> 今日は2020/11/4です。 </code></pre> <p>というコードを書いてきて頭を抱えたので、これはちょっとしっかり教えた方がいいかなと思ったのだった。まぁやりたいことはわかるんだけどね…。</p> <p>日本語であればあまり関係ないかもしれないが、たとえば英語の利用者向けに同じメッセージを同じように書いたら</p> <pre><code class="csharp">DateTime Nov042020 = new DateTime(2020, 11, 4); string formattedText = Nov042020.ToString("It is d.M.yyyy today."); // 結果 -> I午 i0 4.11.2020 午o4a20. </code></pre> <p>となってしまう。コード量は増えるが書式設定とメッセージは切り分けるべきだという流れで<code>string.Format</code>と<code>string-interpolation</code>を上記の例で教えた。</p> <p>以上、余談でした。</p> あぱしょに tag:crieit.net,2005:PublicArticle/16421 2020-12-24T22:18:31+09:00 2020-12-24T22:26:10+09:00 https://crieit.net/posts/C-Dictionary-Value 【C#】DictionaryのValueにクラスをぶちこむ <h1 id="DictionaryのValueにクラスをぶちこみたい!"><a href="#Dictionary%E3%81%AEValue%E3%81%AB%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E3%81%B6%E3%81%A1%E3%81%93%E3%81%BF%E3%81%9F%E3%81%84%EF%BC%81">DictionaryのValueにクラスをぶちこみたい!</a></h1> <p>突然ですか、「DictionaryのValueにクラスをぶちこみたい!」というわけで。。。</p> <h2 id="作るもの"><a href="#%E4%BD%9C%E3%82%8B%E3%82%82%E3%81%AE">作るもの</a></h2> <h3 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h3> <ol> <li>VisualStudio2020</li> <li>.NET Core</li> <li>コンソールアプリ</li> </ol> <h3 id="やりたいこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">やりたいこと</a></h3> <ol> <li>ID(数字)をいれると、IDに紐づいたクラスを取得</li> <li>取得したクラスから名前を表示</li> </ol> <p><del>…クラスとかで分けなくてもいいんじゃね?というのは大いにわかる</del></p> <p>今回は私の大好きなナガノさんのキャラクター、ちいかわとハチワレちゃんの名前を取得します。</p> <h2 id="DictionaryのValueにクラスをぶちこむには…?"><a href="#Dictionary%E3%81%AEValue%E3%81%AB%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E3%81%B6%E3%81%A1%E3%81%93%E3%82%80%E3%81%AB%E3%81%AF%E2%80%A6%EF%BC%9F">DictionaryのValueにクラスをぶちこむには…?</a></h2> <h3 id="ソース"><a href="#%E3%82%BD%E3%83%BC%E3%82%B9">ソース</a></h3> <pre><code class="cs"> Dictionary<int, INagano> NaganoCharacter = new Dictionary<int, INagano>() { {1 , new Chiikawa()}, {2 , new Hachiware()} }; </code></pre> <h3 id="やること"><a href="#%E3%82%84%E3%82%8B%E3%81%93%E3%81%A8">やること</a></h3> <ol> <li>型になるインターフェイスを作る(ここでは<code>INagano</code>)</li> <li>valueにいれたいクラスに継承(ここでは<code>Chiikawa</code>と<code>Hachiware</code>)※newしないとダメだめ</li> </ol> <h2 id="完成したソースがこちら"><a href="#%E5%AE%8C%E6%88%90%E3%81%97%E3%81%9F%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%8C%E3%81%93%E3%81%A1%E3%82%89">完成したソースがこちら</a></h2> <pre><code class="cs">using System; using System.Collections.Generic; namespace ConsoleApp1 { class Program { static void Main(string[] args) { //キャラクターのデータ Dictionary<int, INagano> NaganoCharacter = new Dictionary<int, INagano>() { {1 , new Chiikawa()}, {2 , new Hachiware()} }; //ユーザーに入力を促す Console.WriteLine("住民IDをいれるんだ!!"); //ユーザーが入力 int id = int.Parse(Console.ReadLine()); //入力されたiDからキャラクターのクラスを取得 INagano character = NaganoCharacter[id]; //情報を表示 Console.WriteLine("この子の名前は" + character.Name + "!!!!!!!!!"); } } /// <summary> /// キャラクターのインターフェイス /// </summary> public interface INagano { //おなまえ public string Name { get; } //住民ID public int Id { get; } } /// <summary> /// ちいかわクラス /// </summary> public class Chiikawa : INagano { public string Name { get; } = "ちいかわ"; public int Id { get; } = 1; } /// <summary> /// ハチワレクラス /// </summary> public class Hachiware : INagano { public string Name { get; } = "ハチワレ"; public int Id { get; } = 2; } } </code></pre> ずずまる@ばぶばぶSE tag:crieit.net,2005:PublicArticle/16347 2020-12-12T17:27:51+09:00 2020-12-12T17:27:51+09:00 https://crieit.net/posts/C-URL C#で作ったアプリから標準ブラウザで指定URLを開きたい <p>まあ軽くググって以下の答えが出ますよね。</p> <pre><code>System.Diagnostics.Process.Start("https://play.google.com/store/apps/details?id=com.bushiroad.kemofure"); </code></pre> <p>でも実行すると例外で落ちる。<br /> 「ファイル名を指定して実行」に入力した時と同じ、という説明もあったので、<br /> 窓キー+Rで「ファイル名を指定して実行」にURLを直接入力したら、そっちではちゃんと開く。どうして・・・</p> <p>というわけで、以下のように書きなおしたところ、ちゃんと開きました。</p> <pre><code>System.Diagnostics.Process.Start("cmd", "/C start https://play.google.com/store/apps/details?id=com.bushiroad.kemofure"); </code></pre> <p>例外が出る原因が全くわからないけど、とりあえずこれで。<br /> 一応後者の方もtry-catchは入れとく。</p> uskz tag:crieit.net,2005:PublicArticle/16315 2020-12-09T02:33:59+09:00 2020-12-09T12:41:39+09:00 https://crieit.net/posts/NET-Interactive-C-PowerShell .NET Interactive で C# と PowerShell を股に掛ける <p>この記事は、「<a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2020/csharplang">C# Advent Calendar 2020</a>」 9日目の記事、かつ 「<a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2020/powershell">PowerShell Advent Calendar 2020</a>」 9日目の記事だ。</p> <p>スミマセン。横着した。</p> <p>この時期になれば .NET Interactive の Preview 4 が出てるだろうしその記事でも書こうかな~ などと目論んでいたものの、 残念ながら登場しなかった。</p> <p>このため、 .NET Interactive .NET 5 対応版の導入と、 Variable sharing の内容でお茶を濁そうと思う。</p> <p>.NET Interactive のなんたるかについては、以前の以下の記事で取り扱っているので、そちらを参照願いたい。<br /> <a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/?p=1379">.NET Interactive の C# REPL を Jupyter で | Aqua Ware つぶやきブログ</a></p> <h2 id=".NET Interactive を .NET 5 で動かしてみる"><a href="#.NET+Interactive+%E3%82%92+.NET+5+%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">.NET Interactive を .NET 5 で動かしてみる</a></h2> <p>いきなり本題とは関係ない話になるが、 せっかく .NET 5 がリリースされたところなので、 .NET Interactive を .NET 5 で動かしてみよう。</p> <p>12/8 現在、NuGet で公式にリリースされている .NET Interactive は、 9月にリリースされた <a target="_blank" rel="nofollow noopener" href="https://devblogs.microsoft.com/dotnet/net-interactive-preview-3-vs-code-insiders-and-polyglot-notebooks/">Preview 3</a> に少し更新が加えられた、 <a target="_blank" rel="nofollow noopener" href="https://www.nuget.org/packages/Microsoft.dotnet-interactive/1.0.155302">1.0.155302</a> だ。<br /> これは、 .NET Core 3.1 上に構築されている。</p> <p>一方で 11/27 頃のビルド (<a target="_blank" rel="nofollow noopener" href="https://github.com/dotnet/interactive/commit/61d231529ce8657172c65b389e3524ae72cd30b7">dotnet/interactive@61d2315</a>) あたりでは、既に .NET 5 対応が済まされている。</p> <p>更に、 何故か <a target="_blank" rel="nofollow noopener" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode&ssr=false#version-history">Visual Studio Code の .NET Interactive Notebooks 拡張では、 .NET 5 に対応した 1.0.160204</a> がリリースされている。</p> <p>このため、 現時点で .NET Interactive で .NET 5 を扱う主な方法は、以下の通り。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://mybinder.org/v2/gh/dotnet/interactive/main?urlpath=lab"><img src="https://static.mybinder.org/badge_logo.svg" alt="launch binder" /></a> main ブランチの binder コンテナにアクセス</li> <li>Azure DevOps の CI ビルドの NuGet feed (<a target="_blank" rel="nofollow noopener" href="https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-tools">dotnet-tools</a>) から、 <a target="_blank" rel="nofollow noopener" href="https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-tools&package=Microsoft.dotnet-interactive&protocolType=NuGet&version=1.0.160204&view=overview">Glogal Tool</a> を DL <ul> <li>実行例:<br /> <code>powershell dotnet tool install -g Microsoft.dotnet-interactive --version 1.0.160204 --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json</code></li> </ul></li> <li>VS Code Insider を起動し、 <a target="_blank" rel="nofollow noopener" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode&ssr=false#version-history">.NET Interactive Notebooks 拡張の Preview</a> をインストール</li> </ul> <p>残念ながら、 .NET Interactive インストール済み Jupyter の docker イメージのようなものは 公式では用意されておらず、 コンテナを使いたかったら自分でビルドするしかない。<br /> (そのせいで、 上記 binder の起動は遅い)</p> <p>vscode 拡張だと、 コード補完が ignore case で効くのでとても書きやすい。<br /> 以下、 vscode の .NET Interactive Notebooks 拡張の バージョン 1.0.160204 での実行を前提とするが、 他の環境でも同様に動作するはずだ。</p> <p>なお、 起動した Notebook やコンソールの .NET CLR のバージョンを知りたければ、 System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription を実行すれば良い。<br /> .NET 5.x.x と表示されれば、 .NET 5 で起動していることが確認できる。</p> <p><a href="https://crieit.now.sh/upload_images/601ee4e8bdda70c90dbb844c3f41b00a5fcfb8394c961.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/601ee4e8bdda70c90dbb844c3f41b00a5fcfb8394c961.png?mw=700" alt="dotnet-interactive-variable-sharing-00.png" /></a></p> <h2 id="Variable sharing とは"><a href="#Variable+sharing+%E3%81%A8%E3%81%AF">Variable sharing とは</a></h2> <p>さて、ようやく本題。</p> <p>.NET Interactive が面白いのは、 ひとつの Notebook 内で、 C#, PowerShell, F# の言語間の変数を共有できる点だ。<br /> 悪いなあ、 <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/dotnet/visual-basic/">VB.NET</a>。 このツールは3人用なんだ。</p> <p>この変数を共有できる機能を、 variable sharing という。</p> <p><a href="https://crieit.now.sh/upload_images/6c0880bb18fa55b7ca72b70edf5ea8c45fcfb85010ba9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6c0880bb18fa55b7ca72b70edf5ea8c45fcfb85010ba9.png?mw=700" alt="dotnet-interactive-variable-sharing-01.png" /></a></p> <p>使い方はとても簡単で、 <code>#!share --from</code> マジックコマンドで、 参照する .NET カーネルと変数名を指定するだけだ。</p> <p>但し、 読み込まれている型の空間は共有されない点には注意だ。<br /> 例えば、 C# で定義したクラスのインスタンスを PowerShell で読み込もうとしたり、<br /> PowerShell でロードされているモジュールで定義された型のインスタンスを C# で読もうとすると、エラーになってしまう。<br /> 詳しくは、 <a target="_blank" rel="nofollow noopener" href="https://github.com/dotnet/interactive/blob/main/docs/variable-sharing.md">interactive/variable-sharing.md · dotnet/interactive</a> のドキュメントを参照。</p> <p><a href="https://crieit.now.sh/upload_images/d2e56ff085416a06d06b9561ffd485035fcfb859aa4d5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d2e56ff085416a06d06b9561ffd485035fcfb859aa4d5.png?mw=700" alt="dotnet-interactive-variable-sharing-02.png" /></a></p> <p>但し、 PowerShell でよく使われる <code>PSCustomObject</code> 型については特別対応が入っているらしく、いい感じに <code>Dictionary<string, object></code> に変換されて C# でも扱える。<br /> なにそれ。 PowerShell 内部でも欲しい機能なんだけど。</p> <h2 id="Variable sharing の使いどころ"><a href="#Variable+sharing+%E3%81%AE%E4%BD%BF%E3%81%84%E3%81%A9%E3%81%93%E3%82%8D">Variable sharing の使いどころ</a></h2> <p>例えば…<br /> 例えば……<br /> ………</p> <p>うーん、よい例が思いつかない…</p> <p>そもそも、 C# でできることはだいたい PowerShell でできるし、 その逆もまた然りなんだよな。<br /> 加えて PowerShell では、 <code>Add-Type</code> で手軽に C# のコードを埋め込めるし…</p> <p>強いて挙げるなら、 PowerShell は、付属のコマンドレットや追加モジュールによって、 I/O 操作やリソースの読み込みにが強い一方で、 パイプを交えたデータの処理はだいぶ遅いところだろうか。<br /> 例えば、 PowerShell で ファイルの読み込みなどを行いつつ、 大量のデータの処理は C# で行うといったような使い方はアリかもしれない。</p> <pre><code class="powershell">#!pwsh $data1 = Get-ChildItem *.json -Recurse | Get-Content -Raw | ConvertFrom-Json; $data2 = Get-Clipboard; </code></pre> <pre><code class="csharp">#!csharp #!share --from pwsh data1, data2 data1.Where(kv => kv.Key == "tools") </code></pre> <pre><code class="csharp">#!csharp data2.First() </code></pre> <p>この程度のデータ処理なら PowerShell で全く問題ないレベルだが、 処理するデータの数が増えてくれば C# で実行するメリットが出てくるだろう。</p> advanceboy