- 投稿日:2020-03-28T22:38:51+09:00
LINQでMedian(メディアン)
なぜこのメソッドがない?
LINQを使っていてMax、Averageなどの統計値を出したい、という場面がよくありますが、
なぜこれが標準で計算できないんだ!
と個人的に思う処理に
- メディアン (Median)
- 四分位点
- 標準偏差 (Std)
等がありました。
なので、まずは最も使用頻度が高いメディアンを実装してみました。やりたいこと
その1:シンプルかつ違和感のない処理
例えば和を求めたい場合は、下のコードのように標準ライブラリのSum()を使ってList<int> iList = new List<int> { 1, 2, 3, 4, 5 }; int sum = iList.Sum()のような形で実現可能です。
これに合わせてList<int> iList = new List<int> { 1, 2, 3, 4, 5 }; int median = iList.Median()のようにシンプルに算出したいです。
その2:型依存性なし
入力がint型のとき、double型のとき、long型のとき・・
と全て実装すると可読性が著しく落ちるので、
色んな型を一括で処理できるようにしたいです。実現するには?
その1を実現するためには「拡張メソッド」
その2を実現するためには「ジェネリック」
を使用すれば良いらしいです
(恥ずかしながら今回調べて初めて知りました)こちらを参考にさせて頂きました
コード
アルゴリズム自体はこちらを参照させて頂きました。
本体(DateTime型とそれ以外で処理を分けています)
public static class LinQCustomMethods { // メディアン算出メソッド(Generics) public static T Median<T>(this IEnumerable<T> src) { //ジェネリックの四則演算用クラス var ao = new ArithmeticOperation<T>(); //昇順ソート var sorted = src.OrderBy(a => a).ToArray(); if (!sorted.Any()) { throw new InvalidOperationException("Cannot compute median for an empty set."); } int medianIndex = sorted.Length / 2; //要素数が偶数のとき、真ん中の2要素の平均を出力 if (sorted.Length % 2 == 0) { //四則演算可能な時のみ算出 if (ao.ArithmeticOperatable(typeof(T))) { return ao.Divide(ao.Add(sorted[medianIndex], sorted[medianIndex - 1]), (T)(object)2.0); } else throw new InvalidOperationException("Cannot compute arithmetic operation"); } //奇数のときは、真ん中の値を出力 else { return sorted[medianIndex]; } } // メディアン算出(DateTime型のみ別メソッド) public static DateTime Median(this IEnumerable<DateTime> src) { //昇順ソート var sorted = src.OrderBy(a => a).ToArray(); if (!sorted.Any()) { throw new InvalidOperationException("Cannot compute median for an empty set."); } int medianIndex = sorted.Length / 2; //要素数が偶数のとき、真ん中の2要素の平均を出力 if (sorted.Length % 2 == 0) { return sorted[medianIndex] + new TimeSpan((sorted[medianIndex - 1] - sorted[medianIndex]).Ticks / 2); } //奇数のときは、真ん中の値を出力 else { return sorted[medianIndex]; } } }ジェネリックの四則演算用クラス
こちらを参考にさせて頂きました//ジェネリック四則演算用クラス public class ArithmeticOperation<T> { /// <summary> /// 四則演算適用可能かを判定 /// </summary> /// <param name="src">判定したいタイプ</param> /// <returns></returns> public bool ArithmeticOperatable(Type srcType) { //四則演算可能な型の一覧 var availableT = new Type[] { typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte), typeof(decimal), typeof(double) }; if (availableT.Contains(srcType)) return true; else return false; } /// <summary> /// 四則演算可能なクラスに対しての処理 /// </summary> public ArithmeticOperation() { var availableT = new Type[] { typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte), typeof(decimal), typeof(double) }; if (!availableT.Contains(typeof(T))) { throw new NotSupportedException(); } var p1 = Expression.Parameter(typeof(T)); var p2 = Expression.Parameter(typeof(T)); Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(p1, p2), p1, p2).Compile(); Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(p1, p2), p1, p2).Compile(); Multiply = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(p1, p2), p1, p2).Compile(); Divide = Expression.Lambda<Func<T, T, T>>(Expression.Divide(p1, p2), p1, p2).Compile(); Modulo = Expression.Lambda<Func<T, T, T>>(Expression.Modulo(p1, p2), p1, p2).Compile(); Equal = Expression.Lambda<Func<T, T, bool>>(Expression.Equal(p1, p2), p1, p2).Compile(); GreaterThan = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThan(p1, p2), p1, p2).Compile(); GreaterThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThanOrEqual(p1, p2), p1, p2).Compile(); LessThan = Expression.Lambda<Func<T, T, bool>>(Expression.LessThan(p1, p2), p1, p2).Compile(); LessThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.LessThanOrEqual(p1, p2), p1, p2).Compile(); } public Func<T, T, T> Add { get; private set; } public Func<T, T, T> Subtract { get; private set; } public Func<T, T, T> Multiply { get; private set; } public Func<T, T, T> Divide { get; private set; } public Func<T, T, T> Modulo { get; private set; } public Func<T, T, bool> Equal { get; private set; } public Func<T, T, bool> GreaterThan { get; private set; } public Func<T, T, bool> GreaterThanOrEqual { get; private set; } public Func<T, T, bool> LessThan { get; private set; } public Func<T, T, bool> LessThanOrEqual { get; private set; } }結果
List<int> iList = new List<int> { 1, 2, 3, 4, 5 }; List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 }; List<DateTime> dtList = new List<DateTime> { new DateTime(2020, 3, 24), new DateTime(2020, 3, 25), new DateTime(2020, 3, 26), new DateTime(2020, 3, 27) }; //メディアン(int) Console.WriteLine(iList.Median().ToString()); //メディアン(double) Console.WriteLine(dList.Median().ToString()); //メディアン(DateTime) Console.WriteLine(dtList.Median().ToString());3 3.55 2020/03/25 12:00:00正常にメディアンが出せていそうです
苦労したところ
その1. Generics(T)型の足し算
上のコード中の「要素数が偶数のとき、真ん中の2要素の平均を出力」とありますが、
平均を出すためには足し算、割り算等の四則演算が必要です。
Generics型は「a + b」みたいな形で四則演算ができないので、
こちらを参考に、四則演算クラスを追加しました。※Average()メソッドを使えばGenerics型のまま平均を出せますが、
今後Generic型の処理で四則演算を使いたい場面が多そうなので、クラスとして作成しました。その2. DateTime型の扱い
DateTime型は上記四則演算クラスが適用できないので、別途メソッドを作りました。
DateTime型はMath.Maxクラスも使えなかったりと、色々不便なところが多いですね。その3. Genericsのキャスト
平均を出す際に「2」で割る必要がありますが、
そのままだとGenerics(T)型をint型で割れずにエラーが出るため、
「2」をGenerics(T)型にキャストする必要があります。
こちらを参考に、
「2.0」としてdouble型で宣言 → object型にキャスト → Generics(T)型にキャスト
の順で、Generics(T)型へのキャストが実現できました。そして
上記を実装したあとに、こんなものがあることに気付きました(泣)
リンク
- 投稿日:2020-03-28T22:38:51+09:00
LinQでMedian(メディアン)
なぜこのメソッドがない?
LinQを使っていてMax、Averageなどの統計値を出したい、という場面がよくありますが、
なぜこれが標準で計算できないんだ!
と個人的に思う処理に
- メディアン (Median)
- 四分位点
- 標準偏差 (Std)
等がありました。
なので、まずは最も使用頻度が高いメディアンを実装してみました。やりたいこと
その1:シンプルかつ違和感のない処理
例えば和を求めたい場合は、下のコードのように標準ライブラリのSum()を使ってList<int> iList = new List<int> { 1, 2, 3, 4, 5 }; int sum = iList.Sum()のような形で実現可能です。
これに合わせてList<int> iList = new List<int> { 1, 2, 3, 4, 5 }; int median = iList.Median()のようにシンプルに算出したいです。
その2:型依存性なし
入力がint型のとき、double型のとき、long型のとき・・
と全て実装すると可読性が著しく落ちるので、
色んな型を一括で処理できるようにしたいです。実現するには?
その1を実現するためには「拡張メソッド」
その2を実現するためには「ジェネリック」
を使用すれば良いらしいです
(恥ずかしながら今回調べて初めて知りました)こちらを参考にさせて頂きました
コード
アルゴリズム自体はこちらを参照させて頂きました。
本体(DateTime型とそれ以外で処理を分けています)
public static class LinQCustomMethods { // メディアン算出メソッド(Generics) public static T Median<T>(this IEnumerable<T> src) { //ジェネリックの四則演算用クラス var ao = new ArithmeticOperation<T>(); //昇順ソート var sorted = src.OrderBy(a => a).ToArray(); if (!sorted.Any()) { throw new InvalidOperationException("Cannot compute median for an empty set."); } int medianIndex = sorted.Length / 2; //要素数が偶数のとき、真ん中の2要素の平均を出力 if (sorted.Length % 2 == 0) { //四則演算可能な時のみ算出 if (ao.ArithmeticOperatable(typeof(T))) { return ao.Divide(ao.Add(sorted[medianIndex], sorted[medianIndex - 1]), (T)(object)2.0); } else throw new InvalidOperationException("Cannot compute arithmetic operation"); } //奇数のときは、真ん中の値を出力 else { return sorted[medianIndex]; } } // メディアン算出(DateTime型のみ別メソッド) public static DateTime Median(this IEnumerable<DateTime> src) { //昇順ソート var sorted = src.OrderBy(a => a).ToArray(); if (!sorted.Any()) { throw new InvalidOperationException("Cannot compute median for an empty set."); } int medianIndex = sorted.Length / 2; //要素数が偶数のとき、真ん中の2要素の平均を出力 if (sorted.Length % 2 == 0) { return sorted[medianIndex] + new TimeSpan((sorted[medianIndex - 1] - sorted[medianIndex]).Ticks / 2); } //奇数のときは、真ん中の値を出力 else { return sorted[medianIndex]; } } }ジェネリックの四則演算用クラス
こちらを参考にさせて頂きました//ジェネリック四則演算用クラス public class ArithmeticOperation<T> { /// <summary> /// 四則演算適用可能かを判定 /// </summary> /// <param name="src">判定したいタイプ</param> /// <returns></returns> public bool ArithmeticOperatable(Type srcType) { //四則演算可能な型の一覧 var availableT = new Type[] { typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte), typeof(decimal), typeof(double) }; if (availableT.Contains(srcType)) return true; else return false; } /// <summary> /// 四則演算可能なクラスに対しての処理 /// </summary> public ArithmeticOperation() { var availableT = new Type[] { typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte), typeof(decimal), typeof(double) }; if (!availableT.Contains(typeof(T))) { throw new NotSupportedException(); } var p1 = Expression.Parameter(typeof(T)); var p2 = Expression.Parameter(typeof(T)); Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(p1, p2), p1, p2).Compile(); Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(p1, p2), p1, p2).Compile(); Multiply = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(p1, p2), p1, p2).Compile(); Divide = Expression.Lambda<Func<T, T, T>>(Expression.Divide(p1, p2), p1, p2).Compile(); Modulo = Expression.Lambda<Func<T, T, T>>(Expression.Modulo(p1, p2), p1, p2).Compile(); Equal = Expression.Lambda<Func<T, T, bool>>(Expression.Equal(p1, p2), p1, p2).Compile(); GreaterThan = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThan(p1, p2), p1, p2).Compile(); GreaterThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThanOrEqual(p1, p2), p1, p2).Compile(); LessThan = Expression.Lambda<Func<T, T, bool>>(Expression.LessThan(p1, p2), p1, p2).Compile(); LessThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.LessThanOrEqual(p1, p2), p1, p2).Compile(); } public Func<T, T, T> Add { get; private set; } public Func<T, T, T> Subtract { get; private set; } public Func<T, T, T> Multiply { get; private set; } public Func<T, T, T> Divide { get; private set; } public Func<T, T, T> Modulo { get; private set; } public Func<T, T, bool> Equal { get; private set; } public Func<T, T, bool> GreaterThan { get; private set; } public Func<T, T, bool> GreaterThanOrEqual { get; private set; } public Func<T, T, bool> LessThan { get; private set; } public Func<T, T, bool> LessThanOrEqual { get; private set; } }結果
List<int> iList = new List<int> { 1, 2, 3, 4, 5 }; List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 }; List<DateTime> dtList = new List<DateTime> { new DateTime(2020, 3, 24), new DateTime(2020, 3, 25), new DateTime(2020, 3, 26), new DateTime(2020, 3, 27) }; //メディアン(int) Console.WriteLine(iList.Median().ToString()); //メディアン(double) Console.WriteLine(dList.Median().ToString()); //メディアン(DateTime) Console.WriteLine(dtList.Median().ToString());3 3.55 2020/03/25 12:00:00正常にメディアンが出せていそうです
苦労したところ
その1. Generics(T)型の足し算
上のコード中の「要素数が偶数のとき、真ん中の2要素の平均を出力」とありますが、
平均を出すためには足し算、割り算等の四則演算が必要です。
Generics型は「a + b」みたいな形で四則演算ができないので、
こちらを参考に、四則演算クラスを追加しました。※Average()メソッドを使えばGenerics型のまま平均を出せますが、
今後Generic型の処理で四則演算を使いたい場面が多そうなので、クラスとして作成しました。その2. DateTime型の扱い
DateTime型は上記四則演算クラスが適用できないので、別途メソッドを作りました。
DateTime型はMath.Maxクラスも使えなかったりと、色々不便なところが多いですね。その3. Genericsのキャスト
平均を出す際に「2」で割る必要がありますが、
そのままだとGenerics(T)型をint型で割れずにエラーが出るため、
「2」をGenerics(T)型にキャストする必要があります。
こちらを参考に、
「2.0」としてdouble型で宣言 → object型にキャスト → Generics(T)型にキャスト
の順で、Generics(T)型へのキャストが実現できました。そして
上記を実装したあとに、こんなものがあることに気付きました(泣)
リンク
- 投稿日:2020-03-28T22:18:12+09:00
C#でコンソール画面でのカレンダー作ってみた
Test.csusing System.Collections.Generic; using static System.Linq.Enumerable; using static System.Console; class Sub : object { const int WEEK_MAX = 7;//マジックナンバーは禁止 public Sub() { } public int Week() { WriteLine("1/1の曜日を入力"); switch (ReadLine()) { case "日": case "日曜日": return 1; case "月": case "月曜日": return 2; case "火": case "火曜日": return 3; case "水": case "水曜日": return 4; case "木": case "木曜日": return 5; case "金": case "金曜日": return 6; case "土": case "土曜日": return 7; default: return 0; } } public string Calendar(int year,int you) { if (year <= 0 || you == 0) { return null; } WriteLine($"{year}年カレンダー\n"); var cld = ""; int max; foreach (var y in Range(1, 12)) { cld += $"{y.ToString()}月のカレンダー\n\n"; if (y == 4 || y == 6 || y == 9 || y == 11) { max = 30; } else if (y == 2) { if (year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0)) { max = 29; } else { max = 28; } } else { max = 31; } cld += "日月火水木金土\n"; foreach (var x in Range(1, max)) { if (x < 10) { cld += " "; } if (x == 1) { if (you == 0) { you += 1; } foreach(var z in Range(1, you-1)) { cld += " "; } } if (you % WEEK_MAX == 0) { you %= WEEK_MAX; cld += x.ToString() + "\n"; } else { cld += x.ToString(); } you++; } cld += "\n\n"; } return cld; } public void End() { WriteLine("何かを入力してください"); ReadLine().ToString(); } } class Test { static void Main(string[] args) { var sub = new Sub(); Write("調べたい年を入力"); var year = int.Parse(ReadLine()); var cld = sub.Calendar(year,sub.Week()); WriteLine(cld is null? "\n存在しないです\n":cld); sub.End(); } }
- 投稿日:2020-03-28T19:50:30+09:00
【Unity】3Dミュージックビデオ「clapclap」制作に使用したアセットまとめ
こんにちはっ?八ツ橋まろんです
バーチャルYouTuberとして活動し、ミュージックビデオ(MV)の制作をしています。
先日、バーチャルYouTuberの六葉ミカさんのオリジナル楽曲「clapclap」のMV制作を担当しました。
このMVは動画編集ソフトによるエフェクト追加をせず、全てUnity上で制作しています。
本記事では、このMV制作に使用したUnityアセットやオリジナルのスクリプトを紹介します。ビジュアル関連
unity-audio-spectrum
音源のスペクトルを表示するスクリプト。これがあるだけで音楽感が増してMVに動きがつく。とっても便利。MVでは円形に配置して背景オブジェクトとして使用した。
MaronMotionGraphicsShader
自作の図形描画&アニメーションシェーダー。〇や△☐、☆や♡を描画したり、図形間を行き来したり、回転やuvスクロールしたりできるので汎用性が高い。Animatorコンポーネントと併用してモーショングラフィックスができるように作った。非常に多彩な表現ができるので便利。
MVでは背景やパーティクルなど、あらゆるところに登場する。
MaronOutlineShader
自作のアウトラインシェーダー。RenderTextureを使う方法により太いアウトラインでも破綻しない。且つ、オブジェクトのシェーダーは一切改造しなくていいのでとても便利。MVでは間奏部分の2D演出で活躍した。アウトラインと言ってはいるが、実は上の画像の灰色の影もこれで作っている。技術解説のQiitaの記事はこちら
PostProcesing Stack V2
Package Managerからインストールするアセット。これがある場合とない場合の見た目の違いは、上の画像を見てもらえれば一目瞭然。『盛る』って大事だね。
MVでは特にVignetteとDepth of FieldとColorGradingを使用した。
モーション関連
VR Motion Recorder
HTC Vive と Viveトラッカー3個使って、VR空間でのフルトラッキングのモーション収録環境が構築できるunitypackage。別途Final IKの購入が必要なので注意。トラッカーなしでも動く(その代わり脚は自動のIK)。説明書はVive用だが、Oculus Riftでも動く。動いたモーションをそのままUnityのアニメーションファイルとして書き出すことができ、必要であればそのままキーフレームの削減もしてくれる。MVではダンスのモーションを全てこれで収録し、次のU Motion Proでモーション修正を行った。
U Motion Pro
モーション修正に使える強力なアセット。日本語の解説記事は少ないが、使いこなせればワンランク以上高いクオリティに手が届く。主要な使い方は公式のビデオを参考にすると良い。MVではダンスモーションの修正と、固定ポーズの作成を行った。
カメラワーク関連
Cinemachine
Package Managerからインストールするアセット。CinemachineのVirtual Cameraを大量に作成してTimeline上に並べて重ねることで、カメラワークを作成できる。
MVのカメラワークの大半はCinemachineのVirtual CameraをTimelineで混ぜ合わせて作成した。
サビの一部に使用した素早い動きのカメラワークは、Animatorを付与したVirtual CameraをTimelineで直接キーフレームアニメーションさせて作成した。
PostProcessController
PostProcessing Stack V2のパラメータをTimelineで操作したいために作成した自作スクリプト。カメラを動かしながら被写界深度の距離を変えたいときに重宝した。(実はPostProcessing Stack V2のパラメータはTimeline上で直接変更できないのでこのスクリプトが必要だった。技術記事はこちら)
以上、アセット紹介でした?
MVたくさん見てくれると嬉しいです✨
いつか実際の作成手順やテンプレートなんかを作って解説記事を作りたいなと思っています。それではまたね?
- 投稿日:2020-03-28T18:05:26+09:00
Unity で Visual Studio を使った快適なコーディングをするための環境設定
そのままでは痒いところに手が届かないことが多いので、いろいろカスタマイズして快適にしていきます。
環境
- Unity 2018.4.19f1
- Visual Studio Community 2019
コード分析規則
Linter文化に慣れた結果ゲーム開発でも規則が欲しくなってしまいました。チーム開発では必須だと思っています。
デフォルトだとエディタからの提案がお節介だったり、警告してほしいところで消極的だったりするので、自分好みに設定していきましょう。規則セットの追加
プロジェクト用の規則セットファイルを作成して、Assembly-CSharp と Assembly-CSharp-Editor 設定します。
→規則セットをカスタマイズする.csproj に設定を自動追記
このままではUnityは自動で.csprojファイルを書き換えてしまうので、規則セットファイルを読み込む設定が上書きされないように下記のようなエディタ拡張スクリプトを配置します。
規則セットファイル名は{プロジェクト名}.ruleset
を前提にしています。CsprojFixer.csusing System; using System.IO; using UnityEditor; public class CsprojFixer : AssetPostprocessor { public static string OnGeneratedCSProject(string _, string content) { // プロジェクト名の抽出 string currDirPath = Directory.GetCurrentDirectory(); string[] pathArray = currDirPath.Split(Path.DirectorySeparatorChar); string projectName = pathArray[pathArray.Length - 1]; // .csprojの内容を置換 string old = "</AllowUnsafeBlocks>"; string addition = $"<CodeAnalysisRuleSet>{projectName}.ruleset</CodeAnalysisRuleSet>"; return content.Replace(old, $"{old}{Environment.NewLine} {addition}"); } }StyleCop.Analyzers
もっと規則にこだわりたければNuGetパッケージの StyleCop.Analyzers を導入します。
NuGetパッケージを導入する場合も、エディタ拡張で.csprojにNuGetパッケージ設定を加える処理を追加する必要があります。拡張機能
いくつか普段使っている拡張機能も紹介します。
CodeMaid
スペースを入れてくれたり空行を削除してくれたりする便利なフォーマッタです。
申し分ないくらいのオプション設定をすることができて、保存時の自動整形もできます。(うれしい)普段 VSCode を使っているとこれがないとしんどいです。
Enhanced Syntax Highlighting
シンタックスハイライトでカスタマイズできる種類を少し増やしてくれます。
宣言時だけ色を変えるとかはできないのですが、もともとの設定項目が少ないので少しでもマシになればと思います。参考
- 投稿日:2020-03-28T17:18:38+09:00
DataGridView の最下行で ↓ キーを押すとエラー (原因不明、対処のみ)
DataGridView の最下行で ↓ キーを押すと例外がスローされる。原因不明なまま。対症療法だが、問題が発生しなくなったので記録しておく。
エラーメッセージ
追加情報: '74537' の値は 'Value' に対して有効ではありません。'Value' は 'minimum' と 'maximum' の間でなければなりません。
- 値 '74537' は表示する値によって変わる。
免責事項
このトピックの問題が発生すること、しないこと、問題を解決すること、環境に影響を与えないことを一切保証しない。本トピックの情報は、利用者の責任において使用することとする。
発生状況
- 開発環境: Visual Studio 2015
- ターゲット: .NET Framework 4.6
- プロジェクト: WinForms, C#, VB.NET
Visual Studio のデザイナで、DataGridView のプロパティを設定する。以下に、表示に関係しそうな部分だけ抜粋する。
dgv.AllowUserToAddRows = false; dgv.AllowUserToDeleteRows = false; dgv.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); dgv.RowHeadersVisible = false;Load イベントで DataGridView.DataSource に List<MyModel> を代入してセル高さを自動調整に設定する。表示するテキストは不定長だが、数千文字以上のデータが存在した。
dgv.AutoGenerateColumns = false; dgv.DataSource = myModels.ToList(); dgv.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells; dgv.AutoSize = true;表示後、マウスで一番下までドラッグしても最後の行まで表示されない。さらに、最後の行にフォーカスした状態でキー [↓] を押すと上記の例外がスローされる。
対処
表示する数千文字のテキストを数百文字に減らした。
調整した結果セルの高さが DataGridView? の表示領域より大きくなるデータが存在する場合にこの問題が発生するように見えた。
確認した環境
上記のとおり
- 投稿日:2020-03-28T14:38:57+09:00
【C#入門】『LINQ』の使い方サンプル【4つ紹介】
この記事では、《LINQ》について、
業務を通して学習した内容を、備忘録としてまとめています。
- 『LINQ』 とは…?
- 『LINQ』の使い方サンプル【4つ紹介】
こういった内容についてまとめています。
※本記事は、自分で学習したことのまとめ用として書いています。
尚、解説で誤った点があれば、スローして頂ければ喜んでキャッチしますのでお願い致します。『LINQ』 とは…?
Language Integrated Query の略で、『リンク』と読みます。
公式サイト(MSDN)では、以下のように解説されています。(よく分からないです、、、)
統合言語クエリ (LINQ) は、C# 言語への直接的なクエリ機能の統合に基づくテクノロジのセットの名前です。 これまでは、データに対するクエリは、コンパイル時の型チェックや IntelliSense のサポートがない単純な文字列として表現されてきました。 さらに、SQL データベース、XML ドキュメント、さまざまな Web サービスといったデータ ソースの種類ごとに、異なるクエリ言語を習得する必要がありました。 LINQ では、クエリは、クラス、メソッド、イベントと同様に、ファースト クラスの言語コンストラクトです。
クエリを記述する開発者にとって、最も目立つ LINQ の "統合言語" 部分は、クエリ式です。 クエリ式は、宣言型の "クエリ構文" で記述されます。 クエリ構文を使用することで、フィルター処理、並べ替え、グループ化などのデータ ソースに対する操作を、最小限のコードで実行できます。 同一の基本的なクエリ式のパターンを使用して、SQL データベース、ADO .NET データセット、XML ドキュメントとストリーム、および .NET コレクション内のデータを照会して変換できます。
要は・・・
『LINQ』は…
コレクション(配列やリスト)に対して処理を行うメソッドを集めたライブラリで、
これまで
foreach
などを使って、頑張って実装していた処理を代替してくれます。サンプル①:
Select
で[]
で囲む文字列に変換using System; using System.Collections.Generic; using System.Linq; class MainClass { public static void Main(string[] args) { var number = new List<int>() { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; var changedNumber = number .Select(x => "[" + x + "]"); foreach (var num in changedNumber) { Console.WriteLine(num); } } }[10] [20] [30] [40] [50] [60] [70] [80] [90] [100]サンプル②:
Where
で条件指定using System; using System.Collections.Generic; using System.Linq; class MainClass { public static void Main(string[] args) { var number = new List<int>() { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; var selectedNumber = number .Where(x => x > 50) foreach (var num in selectedNumber) { Console.WriteLine(num); } } }60 70 80 90 100サンプル③:
OrderBy
で並び順を変えるusing System; using System.Collections.Generic; using System.Linq; class MainClass { public static void Main(string[] args) { var number = new List<int>() { 20, 10, 40, 30, 60, 50, 80, 70, 100, 90 }; var orderedNumber = number .OrderBy(x => x) //.OrderByDescending(x => x) foreach (var num in orderedNumber) { Console.WriteLine(num); } } }10 20 30 40 50 60 70 80 90 100サンプル④: 各種演算
using System; using System.Collections.Generic; using System.Linq; class MainClass { public static void Main(string[] args) { var number = new List<int>() { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; Console.WriteLine("平均: " + number.Average()); Console.WriteLine("合計: " + number.Sum()); Console.WriteLine("最大値: " + number.Max()); Console.WriteLine("最小値: " + number.Min()); Console.WriteLine("要素数: " + number.Count()); } }平均: 55 合計: 550 最大値: 100 最小値: 10 要素数: 10
- 投稿日:2020-03-28T13:33:22+09:00
Visual Studio ソリューションテンプレート
1.環境
Visual Studio 2019 Community 2019 Version 16.4.3
Windows10 64bit 18092.テンプレートの作成
2-1.プロジェクトの作成
[C#]-[Windows]-[機能拡張]を選択し、"C# Project Template"を選択。
#2-2.参考情報
https://docs.microsoft.com/ja-jp/visualstudio/ide/how-to-create-multi-project-templates?view=vs-2019
https://www.ecanarys.com/Blogs/ArticleID/180/Create-custom-project-templates-in-Visual-Studio
3. ソリューションテンプレートの作り方
" 3-1. ソリューションテンプレートファイルの修正
.vstemplateファイルのTypeを"Project"から"ProjectGroup"にする。
ProjectCollectionタグにProjectTemplateLinkを追加する。
<ProjectCollection> <ProjectTemplateLink ProjectName="$projectname$.Domain" CopyParameters="true"> DDDSample.Domain\MyTemplate.vstemplate </ProjectTemplateLink> <ProjectTemplateLink ProjectName="$projectname$.Infrastructure" CopyParameters="true"> DDDSample.Infrastructure\MyTemplate.vstemplate </ProjectTemplateLink> </ProjectCollection>3-2.各プロジェクトファイルのテンプレート作成
ソリューションに含まれているプロジェクトの数だけ、テンプレートのエクスポートを実行する。
・テンプレートの種類の選択では「プロジェクトのテンプレート」を選択
・テンプレートのオプションの選択では「テンプレートを自動的にVisual Studioにインポート」のチェックを外す作成されたzipは展開する。
3-3.各プロジェクトの依存関係の設定
プロジェクト内の*.csprojファイルを編集する必要がある。
例として「MyApp」というソリューションに「MyApp.Domain」と「MyApp.Infrastructure」というプロジェクトが存在するとする。
InfrastructureはDomainを参照する場合について説明する。なお
「MyApp」はテンプレートなので可変で、Domain、Infrastructureは固定となる。Infrastructureのcsprojファイルを開き、以下のように設定する。
<ItemGroup> <ProjectReference Include="..\$ext_safeprojectname$.Domain\$ext_safeprojectname$.Domain.csproj"> <Project>{97b2ddcf-cb5a-4e25-aa31-671f23f18f1f}</Project> <Name>$ext_safeprojectname$.Domain</Name> </ProjectReference> </ItemGroup>
- 投稿日:2020-03-28T13:03:01+09:00
【UTAUプラグイン】半音上げプラグイン大会にお世話になったので恩返ししてみるの巻 with utauPlugin&MaterialDesignInXAMLToolkit
UTAUプラグインを簡単に作るための「utauPlugin」と
見た目をイイ感じにするための「Material Design In XAML Toolkit」を使って
「例のアレ」を作ります。はじめに
UTAUプラグイン作りのためプログラミングを始めて早1年3ヶ月。
Twitterに教えてくれる方がたくさんいたりDiscordにプログラミング相談所があったりするおかげで、UTAUプラグインとか関連ツールとか全然関係ないAndroidのアプリとか色々作れるようになって楽しいです。時間が足りない。完全に先人たちのおかげでここまで来れたわけですが、「【UTAU】半音上げプラグイン大会」というのがありまして、これがサンプルコードの宝庫になってます。
その後、我らがデルタ先生がC#でUTAUプラグイン作る用のクラスライブラリ「utauPlugin」というのを作ってくださって、これ無くしてはプラグイン作りとかやってられないぐらい便利なので、
今回はこれを使って半音上げプラグインを作っていこうと思います。ついでなので見た目もイイ感じにします。
作りたいもの概要
・半音上げプラグイン
→音程を半音上げるだけのプラグイン
実用性は特にないけどシンプルなのでサンプルにぴったり・言語はC#、開発環境はVisual Studio Community 2019
→Windows用ソフト作るときのベーシックな環境(たぶん)・WPF
→C#で作れるものは色々あって、操作画面が必要なプラグインは「WPF」で、画面がいらないプラグインは「コンソールアプリ」で作るのがいいと思う・「utauPlugin」とは
→UTAUが出力したデータを読んで、扱いやすく整理し、処理後のデータを本体に返してくれる便利なやつ・「Material Design In XAML Toolkit」とは
→WPFの見た目のパーツたちを「マテリアルデザイン」にしてくれるやつ
マテリアルデザインとはGoogleが推してるUIのデザインの考え方で、立体感やアニメーションがついててかわいい
某Windowsのペラペラなボタンとは段違いである作り始める前に
「C#でUTAUプラグイン開発を始める方法」にutauPluginの導入のしかたが書いてあります。
というかコンソールアプリ版半音上げプラグインの作り方がまんま書いてあります。
HelloWorldか?ってぐらいの簡素さでプラグインが書けます。Material Design In XAML Toolkitに関しては、Visual StudioのNuGetで「MaterialDesignThemes」と検索して出てくるやつをインストールしてください。
GitHubにDemoAppがあるので落としとくと便利です。C#の知識的には基本的な代入や四則演算とfor、if、Listぐらいわかれば十分かと。
配列はグループ機能的なやつですがListが駆逐したんで(してない)Listだけ覚えればとりあえず大丈夫です。完成品
実際のコード
App.xaml
App.xaml<Application x:Class="OneUp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:OneUp" StartupUri="MainWindow.xaml" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"> <Application.Resources> <!-- MaterialDesignThemeの設定を読み込む(基本色を選ぶ) --> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" /> <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" /> <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Lime.xaml" /> <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Amber.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>ソフトの基本設定みたいなのが書いてあるApp.xaml
<ResourceDictionary Source
~の一行目でLightかDarkか選ぶ
2行目で各パーツのデザインをロード(たぶん)
3行目で基本色を、4行目でサブの色を選ぶ
OneUpとは緑のキノコのことですMainWindow.xaml
MainWindow.xaml<Window x:Class="OneUp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:OneUp" mc:Ignorable="d" FontSize="13" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip" Title="半音上げ" Height="200" Width="470" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"> <Grid> <!-- ウィンドウ内を上下に分ける --> <!-- 下半分のサイズはボタンに合わせて、上半分のサイズは残り全部 --> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <!-- 上半分 --> <!-- StackPanelに入れちゃえばだいたい何でも揃う --> <StackPanel Margin="20"> <TextBlock TextWrapping="Wrap"> <Run Text="↓Material Design In XAML ToolkitのCheckBoxがかわいいから使いたいだけ" /><LineBreak/> <Run Text=" アクセントカラーにしてみたぞ" /> </TextBlock> <CheckBox Content="休符も上げる" x:Name="R" IsChecked="True" Style="{StaticResource MaterialDesignAccentCheckBox}" /> </StackPanel> <!-- 下半分 OKとキャンセル --> <!-- 横方向に並ぶタイプのStackPanelをGridの下半分に入れる --> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1" VerticalAlignment="Bottom"> <Button Content="OK" Name="ok" Margin="10,10,10,20" Width="90" Click="OK" /> <Button Content="キャンセル" Margin="10,10,20,20" Width="90" Click="Cancel" /> </StackPanel> <!-- DialogとDrawer --> <!-- MaterialDesignの機能。ポップアップとかドロワー出すときに使う --> <materialDesign:DialogHost x:Name="dialogHost" Grid.RowSpan="2" /> <materialDesign:DrawerHost x:Name="errorDrawer" Grid.RowSpan="2" > <materialDesign:DrawerHost.BottomDrawerContent> <TextBlock Margin="20" x:Name="errorText" VerticalAlignment="Center" HorizontalAlignment="Center"/> </materialDesign:DrawerHost.BottomDrawerContent> </materialDesign:DrawerHost> </Grid> </Window>見た目を書くMainWindow.xaml
HTML手打ちポチポチマンだったから親しみやすさはんぱねぇWindow:
WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip"
ウィンドウを真ん中に出す、ウィンドウサイズを変えられるようにする
だいたいいつもこの設定です
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
Material Design In XAML Toolkitが用意したコントロールを使いたいなら書く
(コントロールとは、ボタンとかのパーツのこと)GridとかStackPanelとか:
コントロールを入れるためのコントロール
「WPF 個人用メモ.1」が参考になりますMaterial Design In XAML Toolkitのコントロールたち:
DialogHost:
今回は使ってませんがUserControlでポップアップ画面を作ってdialogHostに入れて出せばポップアップします
DrawerHost:
下からドロワーがにょきっと出ます
エラーが出たときにドロワーにエラーメッセージ出す用です
(このデザインだとMessageBox似合わないので・・・)
↓こんなやつ
MainWindow.xaml.cs
MainWindow.xaml.csusing System; using System.Windows; using System.Windows.Input; //忘れずに書いて using utauPlugin; namespace OneUp { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { // ここに書いた変数とかはMainWindowクラスのどこからでも呼び出せるよゾーン private UtauPlugin utauPlugin; // ↓コマンドライン引数ってやつ // この配列の0番目にこのソフトの場所が、 // 1番目にUTAUが出力したプラグイン用データの場所が書いてある string[] args = Environment.GetCommandLineArgs(); public MainWindow() // 起動時に実行 { // たぶんxamlを読んでる InitializeComponent(); // utauPluginを起動してデータの場所を教える utauPlugin = new UtauPlugin(args[1]); // utauPluginがデータを分析する utauPlugin.Input(); // Enter押す=OKボタン押すのと同義 にする KeyDown += (sender, e) => { if (e.Key != Key.Enter) { return; } OK(sender, e); }; } // OKボタン押すと実行 private void OK(object sender, RoutedEventArgs e) { try { // utauPlugin.noteは音符(Note)がずらーっと入ったList // Listの中身をひとつずつ取り出して処理するforeach // 今処理してるNoteをnoteと呼ぶ foreach (Note note in utauPlugin.note) { // 休符かどうか確認 if (note.GetLyric() == "R" || note.GetLyric() == "r") { if(R.IsChecked == false) // 休符かつCheckBox「R」にチェックがないとき、 { continue; // このNoteの処理をやめて次のNoteにいく } } // 新しい音程は今の音程+1 note.SetNoteNum(note.GetNoteNum() + 1); } // UTAU本体に処理したデータを返す utauPlugin.Output(); } catch // try中にエラー吐いたとき、落ちない代わりに↓が実行される { ErrorOpen("処理に失敗しました"); // この内容は後述 return; // OKメソッドをやめる(次のCloseは起きない) } Close(); // ソフトを終了 } // キャンセルボタン押すと実行 private void Cancel(object sender, RoutedEventArgs e) { Close(); } //エラードロワーを開く public void ErrorOpen(string text) { // xamlに書いたTextBlockにエラーメッセージを設定 errorText.Text = text; // 警告音を鳴らす System.Media.SystemSounds.Exclamation.Play(); // ドロワーを出す errorDrawer.IsBottomDrawerOpen = true; } } }MainWindow.xamlと対になる処理部分
ググるときのTips
余談ですが、UI(見た目)絡みで調べたいことは「WPF Button」のように、
それ以外で調べたいことは「C# List ソート」のようにググるといいです。おわりに
無駄に操作画面作ったり見た目をイイ感じにしたりドロワー出したりしたせいでけっこう長くなりましたが、
ust読んで各音符の情報を仕分けするのがutauPlugin.Input();
、
必要な情報だけをまとめて何行も出力するのがutauPlugin.Output();
だけで済むので
UTAUプラグインとしてはめっっっっちゃ簡素になってると思います。
デルニャンサイコー!ドロワー使わないなら、エラー通知は
MessageBox.Show("エラーメッセージ");
の1行で済みます。
Material Design In XAML Toolkitを導入しないならApp.xamlはデフォルトのままでok。こんな感じで作ったプラグインたちをうちの調声メモブログで配布してますのでよろしくお願いします。
ハモリ作成とか表情音源ピッカーとかビブラートストッカーとか、
あとUTAU音源をインストーラー型で配布するソフトとかprefix.mapエディタもあるよ。あと本業は調声なんでniconicoのマイリスもよろしくお願いします!
- 投稿日:2020-03-28T11:34:37+09:00
フォーム上にあるコントロールを、右クリックでコンテキストメニューを表示して、フォーム上から動的に削除する(その2:削除したいコントロールが複数ある場合)
前回紹介した「フォーム上にあるコントロールを、右クリックでコンテキストメニューを表示して、フォーム上から動的に削除する(その1)」に引き続き、若干の変更を加え、今回は複数のコントロール(今回は、PictureBoxが2つ、Panelが1つ)がある場合についてのソースコードを紹介する。
本動作に必要な主なクラス・メソッドなどは次の通り。
ContextMenuStrip.SourceControlプロパティまた、ContextMenuStripのインスタンスcontextMenuStripOnControlを異なったメソッド間で使用するために、その変数宣言をクラス直下で行うよう変更した。
以下にソースコードを示す。
using System; using System.Drawing; using System.Windows.Forms; namespace TestContextMenu { class Form1 : Form { static void Main() { Application.Run(new Form1()); } ContextMenuStrip contextMenuStripOnControl; // (変更)前回 Form()コンストラクタにあったものをクラス直下の変数とした。 public Form1() { contextMenuStripOnControl = new ContextMenuStrip(); // コントロール上で表示するコンテキストメニュー // pictureBox1の定義 PictureBox pictureBox1; pictureBox1 = new PictureBox(); pictureBox1.Location = new Point(50, 50); pictureBox1.Size = new Size(100, 100); pictureBox1.Image = Image.FromFile("play100_100.png"); pictureBox1.BorderStyle = BorderStyle.FixedSingle; pictureBox1.Parent = this; // フォームにpictureBox1を追加する // pictureBox2の定義 (追加変更) PictureBox pictureBox2; pictureBox2 = new PictureBox(); pictureBox2.Location = new Point(160, 50); pictureBox2.Size = new Size(100, 100); pictureBox2.Image = Image.FromFile("pause.png"); pictureBox2.BorderStyle = BorderStyle.FixedSingle; pictureBox2.Parent = this; // フォームにpictureBox2を追加する // panel1の定義 (追加変更) Panel panel1; panel1 = new Panel(); panel1.Location = new Point(50, pictureBox1.Location.X+pictureBox1.Height + 10); panel1.Size = new Size(210, 50); panel1.BackColor = Color.Blue; panel1.Parent = this; // フォームにpanel1を追加する // pictureBox1の上で表示するコンテキストメニュー ToolStripMenuItem tsmiDelete = new ToolStripMenuItem("削除(&D)"); // コンテキストメニューで表示される項目 D tsmiDelete.Click += new EventHandler(tsmiDelete_Click); // コンテキストメニューの中で「削除」を選択した時のデリゲート contextMenuStripOnControl.Items.Add(tsmiDelete); // コンテキストメニューにtsmiDeleteを追加する // 削除したいコントロールのContextMenuStripプロパティーにContextMenuStripのインスタンスを指定する。コントロール(pictureBox1,2とpanel1)の上で左クリックしたときに、ContextMenuStripのインスタンスのインスタンスであるcontextMenuStripOnControlに登録されたコンテキストメニューが表示されるようになる。 pictureBox1.ContextMenuStrip = contextMenuStripOnControl; pictureBox2.ContextMenuStrip = contextMenuStripOnControl; // (追加変更) panel1.ContextMenuStrip = contextMenuStripOnControl; // (追加変更) } // コントロール(pictureBox1,2とpanel1)の上でコンテキストメニューの「削除」を選択した時のイベントハンドラ void tsmiDelete_Click(object sender, EventArgs e) { //Control c = contextMenuStripOnControl.SourceControl as Control; //下でも、或いはこのような記述でも可(但し全てもコントロールオブジェクトが消える (変更) PictureBox c = contextMenuStripOnControl.SourceControl as PictureBox;// PictureBoxのオブジェクトのみをフォームから消す時 (変更) if (c != null) // (追加変更) { this.Controls.Remove(c); // フォームに登録されたコントロール(PictureBox)のオブジェクトを消す } else MessageBox.Show("選択したのはPictureBoxではありません!"); // (追加変更) } } }
- 投稿日:2020-03-28T10:31:02+09:00
フォーム上にあるコントロールを、右クリックでコンテキストメニューを表示して、フォーム上から動的に削除する(その1)
フォーム上に配置したコントロール(ここではピクチャーボックス)を動的に削除する方法のソースコードを紹介する。
削除前 削除後
(右クリックでコンテキストメニューを (コントロールがフォームから消えたところ)
表示させたところ)削除する際に、対象となるコントロールの上にマウスポインタを置き、右クリックでコンテキストメニューを表示させ、削除の項目を選択して削除する。
本動作に必要な主なクラス・メソッドなどは次の通り。
ContextMenuStrip クラス
ToolStripMenuItem クラス
ToolStripMenuItem.Click デリゲート
EventHandler() イベントハンドラ
Control.ContextMenuStrip プロパティ
Control.Remove() メソッド
C#// 参考資料 http://kaitei.net/csforms/menu-strips/#item-67 using System; using System.Drawing; using System.Windows.Forms; namespace TestContextMenu { class Form1 : Form { PictureBox pictureBox1; static void Main() { Application.Run(new Form1()); } public Form1() { ContextMenuStrip contextMenuStripOnPictureBox1; contextMenuStripOnPictureBox1 = new ContextMenuStrip(); // pictureBox1上で表示するコンテキストメニュー // pictureBox1の定義 pictureBox1 = new PictureBox(); pictureBox1.Location = new Point(100, 100); pictureBox1.Size = new Size(100, 100); pictureBox1.BorderStyle = BorderStyle.FixedSingle; pictureBox1.Image = Image.FromFile("play100_100.png"); pictureBox1.Parent = this; // フォームにpictureBox1を追加する // pictureBox1の上で表示するコンテキストメニュー ToolStripMenuItem tsmiDelete = new ToolStripMenuItem("削除(&D)"); // コンテキストメニューで表示される項目 tsmiDelete.Click += new EventHandler(tsmiDelete_Click); // コンテキストメニューのなかで「削除」を選択した時のデリゲート contextMenuStripOnPictureBox1.Items.Add(tsmiDelete); // コンテキストメニューにtsmiDeleteを追加する pictureBox1.ContextMenuStrip = contextMenuStripOnPictureBox1; // pictureBox1の上で左クリックしたときに、contextMenuStripOnPictureBox1に登録されたコンテキストメニューが表示されるようにする } // pictureBox1の上でコンテキストメニューを表示させたときのイベントハンドラ // 「削除」を選択した時実行される void tsmiDelete_Click(object sender, EventArgs e) { this.Controls.Remove(pictureBox1); // フォームのコントロールからpictureBox1を消す } } }
- 投稿日:2020-03-28T09:32:07+09:00
Blazor WebAssemblyのアプリケーションをVisual Studioでデバッグする
概要
Blazor WebAssembly 3.2.0 Preview 3がリリースされ、Visual Studio(とVisual Studio Code)のデバッグに対応したようなので、Visual Studioでのデバッグを試した際のメモです。
環境
Windows 10(64bit) 1909
Google Chorome 80.0.3987.149 (64bit).NET Core SDK 3.1.300-preview-015048
Microsoft.AspNetCore.Blazor 3.2.0-preview3.20168.3
Visual Studio 2019 Version 16.6.0 Preview 2.0セットアップ
最新の.NET Core SDK(3.1.201) のインストール
下記から最新バージョンのSDKをインストールします。
リンクテンプレートの更新
下記のコマンドで最新のBlazor WebAssembly templateをインストールします。
dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview3.20168.3Visual Studio 2019 Previewのインストール
下記から最新のプレビュー版のVisual Studioをインストールします。(16.6.0 Preview 2.0)
リンク私は試していませんが、最新プレビュー版をインストールすると、上記のSDKとテンプレートも合わせてインストールされるとのことですので、Preview版のインストールだけを実施だけでも良いかもしれません。
Blazor WebAssemblyアプリ作成
アプリの作成
Preview版のVisual Studioを起動し、新規にBlazor WebAssemblyのアプリを作成します。
(リリース元の説明ですと、ASP.NET Core hostedを使用した場合の説明となっていましたが、チェックをつけない場合でもデバッグできました。)デバッグ
デバッグ時のブラウザ指定
私の環境では、Edgeをデバッグ時のブラウザに指定すると、デバッグができませんでした。
(Microsoft Edge 44.18362.449.0)Choromeを指定することでデバッグ可能になったので、起動ブラウザを変更しました。
デバッグ実行
あとは、通常通りデバッグ実行を開始します。
おわりに
簡単にですが、Visual Studioを用いたBlazor WebAssemblyアプリのデバッグ方法を紹介しました。
まだプレビュー版ですが、使い慣れたVisual Studioでデバッグできるのは嬉しいですね。説明を見ていた時には今回はASP.NET Core hosted版だけの対応かと思いましたが、未ホスト版でも使えたのは良かったです。
- 投稿日:2020-03-28T08:14:36+09:00
[C#]文字列補間で遊ぶ
C#の文字列補間という機能をしっていますか?
以下のように文字列の中に変数を埋め込める機能です。var s = "world"; Console.WriteLine($"hello {s}"); // hello worldC#に限らずほとんどの言語で使用できる機能です。
C#独自の要素として文字列補間を使用した場合、FormattableString
として変数に代入できるというものがあります。// 左辺をFormattableStringにできる FormattableString s = $""; // 文字列補間を使用しない場合は代入できない // 型 'string' を 'System.FormattableString' に暗黙的に変換できません FormattableString s = ""; // varを使用した場合はstringになる var s = $"";参考:文字列補間を使用してカルチャ固有の結果文字列を作成する方法
この仕様自体はカルチャ関連のために存在しているみたいですがほとんど使用したことがないのでよくわかりません。
今回はこの機能を使用して遊んでみます。
実用性は多分ないです。FormattableStringの仕様
FormattableString
は文字列補間に使用されるフォーマット、渡された引数を持ったオブジェクトです。var s = "world"; FormattableString fs = $"hello {s}!"; Console.WriteLine(fs.Format); Console.WriteLine(fs.ArgumentCount); Console.WriteLine(fs.GetArguments()[0]); // 出力 // hello {0}! // 1 // worldポイントはフォーマット文字列と渡された引数が分けて取得できることです。
つまり以下ようなメソッドを書くことができます。// 挿入される文字列を2倍にするメソッド string DoubleInsert(FormattableString fs) { var f = fs.Format; for (var i = 0; i < fs.ArgumentCount; i++) { var target = "{" + i + "}"; f = f.Replace(target, target + target); } return string.Format(f, fs.GetArguments()); } var s = "world"; Console.WriteLine($"hello {s}!"); Console.WriteLine(DoubleInsert($"hello {s}!")); // 出力 // hello world! // hello worldworld!
何に使えるかわからない非常に面白い機能だと思います。
何とかして有効活用できる方法を探します。テンプレートとして使用する
長い文字列を生成する際に以下のようなメソッドを作るかもしれません。
static string Template(int i, int j) { return $@" i: {i} j: {j} "; }この文字列をそのまま使用するなら大した問題はありませんが例えばこの文字列をパースする場合を考えます。
つまりこういうことです。string Template(int i, int j) { return $@" i: {i} j: {j} "; } Dictionary<string, string> CreateDictionary(int i, int j) { var dict = new Dictionary<string, string>(); // 文字列を作る var s = Template(i, j); // 文字列をパースしてDictionary<string,string>に変換する foreach (var line in s.Split('\n')) { var x = line.Split(":"); if (x.Length != 2) continue; dict[x[0].Trim()] = x[1].Trim(); } return dict; } var d = CreateDictionary(100, 200); foreach (var kv in d) { Console.WriteLine($"key:{kv.Key} value:{kv.Value}"); } // 出力 // key:i value:100 // key:j value:200上記の
CreateDictionary
はあんまり効率が良くないので書き直すことを考えます。
理想は以下の形ですがこれではあまり柔軟性がありません。Dictionary<string, string> CreateDictionary(int i, int j) { return new Dictioanry<string, string>() { ["i"] = i.ToString(), ["j"] = j.ToString(), }; }ある程度汎用的にするなら目指すのは以下のような形でしょうか。
var template = @" i: _ j: _ "; // テンプレートからキーの部分のみを取り出す var keys = GetKeys(template); Dictionary<string, string> CreateDictionary(object[] values) { var dict = new Dictionary<string, string>(); for (var i = 0; i < keys.Length; i++) { dict[keys[i]] = values[i].ToString(); } return dict; }これを文字列補間を使用して何とかできないか考えてみましたが最終的に以下のような書き方ができるようになりました。
実装は長いので省きます。
興味がある方はこちらを参照してください。// DictionaryBuilder.Createでテンプレートを作成 // ネストも可能 var builder = DictionaryBuilder.Create<(int i, int j)>(p => $@" i: {p.P(t => t.i)} j: {p.P(t => t.j)} jj: {p.P(t => t.j + t.j)} dict: {p.P(t => t.i, DictionaryBuilder.Create<int>(pp => $@" s: 1 v: {pp.P(v => v)} "))} "); // テンプレートを元にDictionaryを作成 foreach (var kv in builder.ToDictionary((30, 40))) { if (kv.Value is Dictionary<string, object> dic) { Console.WriteLine($"key:{kv.Key}"); foreach (var kv2 in dic) { Console.WriteLine($" key:{kv2.Key} value:{kv2.Value}"); } } else { Console.WriteLine($"key:{kv.Key} value:{kv.Value}"); } } /* output key:i value:30 key:j value:40 key:jj value:80 key:dict key:s value:1 key:v value:30 */なんかそれっぽい形になったと思います。
ゼロアロケーション文字列補間
1バイトもアロケーションしたくない人にとっては素の文字列補間は使用できません。
ゼロアロケーションを目指す場合はフォーマット文字列と引数を別々に渡し、フォーマット文字列をパースしつつ事前に確保しておいたバッファに文字をためていくという実装になります。
例えばUnityのTextMeshProは以下の形でゼロアロケーションを実現しています。TextMeshPro text = /* ... */; // SetTextを行うとTextMeshProが内部に持っているバッファに文字情報が設定される // 内部に持っているバッファはcharの配列なので文字列をアロケーションする必要がない // 逆に言うと文字列にできないのでフォーマットのパースやchar配列への変換などは全部自力でやらなければならない text.SetText("i: {0} j: {1}", 1, 2);文字列補間を使用したゼロアロケーションは以下のような書き方ができるようになりました。
実装はこちらです。// 文字情報を内部のchar配列にためるBuilder var sb = new CharBufferedStringBuilder(); // 文字列補間からテンプレートの作成 var fsb = new FormatStringBuilder<(int i, int j)>(p => $"i: {p.P(t => t.i)}, j: {p.P(t => t.j)}"); // テンプレートを適用 // バッファサイズが足りなくならない限りはゼロアロケーション // 適用時に毎回パースしなくて済むので多少効率的(かもしれない) fsb.Apply(sb, (100, 200)); Console.WriteLine(new string(sb.Buffer, 0, sb.Length)); /* output i: 100, j: 200 */最後に
今回の記事はjavascriptのタグ付けされたTemplate literalがうらやましかったので作成しました。
タグ付けされたTemplate literalの使用用途について探してもstyled-componentsかgraphql-tagくらいしか見つけられなかったのでC#ではあまり使えない予感がします。
実用的に使えそうなものがあればぜひ教えてください。実装したコードは以下に置いています。
yaegaki/StringInterpolationUtil