20210724のC#に関する記事は7件です。

C#の基本イディオムについてその4~文字列の判定~

C#でコードを書く際に文字列を使用する場合の基本的なイディオムについてまとめた。 文字列が同じかどうかを判定する (例)string型の変数であるnameが"冨岡義勇"かどうかを判定したい場合 if(name == "冨岡義勇") 文字がnullか空文字かどうかを判定する (例)string型の変数であるnameがnullか空文字かどうかを判定したい場合 if(String.IsNullOrEmpty(name)) こうすることで、 ・name == "" ・name.Length == 0 ・name == null などのint型、string型の場合の処理をまとめてやってくれる。 値がnullでないことが分かっており、空文字が返ってくる可能性がある場合 その場合は、空文字かどうかを判定すればいいだけなので、 if (name == String.Empty) もしくは if (name == "") で判定する。 個人的には、 コードの短さと分かりやすさから後者を採用したい。 空白だけが入力されているかも判定したい場合 場合によっては、" "のように空白しか入力されていない場合があるが、これかどうかを判定したい場合は、 if(String.IsNullOrWhiteSpace(name)) と書くことで、null、" "かどうかを判定できる。 値の頭に特定の文字列があるかどうかを判定する (例)string型の変数であるnameの頭に、「"水柱・"」という値が含まれているかどうかを判定したい場合 if(name.StartsWith("水柱・")) 値の後に特定の文字列があるかどうかを判定する (例)string型の変数であるnameの後に、「"様"」という値が含まれているかどうかを判定したい場合 if(name.EndsWith("様")) Linqを使うパターン 値の中に特定の文字があるかどうかを判定する (例)string型の変数であるnameに、「・」があるかどうかを判定したい場合、 if (name.Contains('・')) LINQのContainsメソッドを使用する。 文字列のすべてをチェックするパターン (例1)パスワードなど、大文字を1文字以上入れるなどの処理が必要な時に、入力されたstring型の変数に大文字が1文字以上入っているかどうかチェックしたい場合 if(name.Any(el => Char.IsUpper(el))) LINQのAnyメソッドを使うことで、 値を1文字ずつチェックし1文字でも、条件を満たしている文字があれば以降の処理をせずに、Trueを返す。 この場合、IsUpperメソッドで大文字があれば、Trueを返す。 (例2)金額や年齢など、数値のみ代入されているかどうかを判定したい場合 if(name.All(el => Char.IsDigit(el))) LINQのAllメソッドを使うことで、値を1文字ずつチェックし、全て条件を満たしていればTrueを返す。 この場合は、IsDigitメソッドで値が数値かどうかを判定し、Trueを返す。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rx部品の命名案

はじめに 以前、下記記事にイベントハンドラの命名案を記載しました。 RxはC#のevent構文の上位互換と言われています。 では、同じ解釈を適用する場合、RxのIObservable等の部品の命名パターンはどのように構築すれば良いのか考えてみます。 今回の内容は下記記事に記載したRxの解釈に基づいたものとなります。 Rx全般に適用可能だと思いますが、ここではUniRxのみを検討範囲とします。 クラス別命名案 以下の形で整理できるのではないかと考えました。 Subject Subjectという型の名前自体は、恐らく「主題」という意味で使用しているのではないかと思います。 Observerと繋がるように「観測テーマ」と意訳しても良いと思います。 イベントを発火させる役割で利用する場合は、Subjectはイベントのフィールドの置き換えとなります。 イベント構文でのフィールド宣言部分 private EventHandler eventHandler; Rxでのフィールド宣言部分 private readonly Subject<Unit> eventHandler = new Subject<Unit>(); このため、イベントハンドラと同じ命名方法が使えそうです。 イベントハンドラの命名例 public class Enemy { public event EventHandler OnDead; } イベントハンドラの命名例をSubjectに適用 public class Enemy { private readonly Subject<Unit> onDead = new Subject<Unit>(); } Observable event構文を置き換える場合、Observableはイベントプロパティを置き換える部分となります。 イベント構文でのイベントプロパティ private EventHandler eventHandler; public event EventHandler EventHandler { add { this.eventHandler += value; } // removeの方は検討範囲外なので省略 } RxでのObservable公開部分 private readonly Subject<Unit> eventHandler = new Subject<Unit>(); public IObservable<Unit> EventHandler => this.eventHandler; このため、こちらもイベントハンドラ名と同じ命名方法でいけそうです。 イベントハンドラの命名例 public class Enemy { public event EventHandler OnDead; } イベントハンドラの命名例をObservableに適用 public class Enemy { private readonly Subject<Unit> onDead = new Subject<Unit>(); public IObservable<Unit> OnDead => this.onDead; } Observer Observerそのものについては、インスタンスに名前を付けて保持することがまずないので、検討対象外とします。 Observerに登録する、イベント発火時に呼び出すメソッドの登録処理は以下のように置き換わります。 イベント構文でのイベント購読処理 this.EventHandler += this.OnFired(); RxでのObservable購読処理 this.EventHandler.Subscribe(_ => this.OnFired()); このため、こちらはイベントハンドラのメソッド名と同じ考え方が使えそうです。 イベントハンドラのメソッド名の命名例 public class Enemy { public event EventHandler OnDead; } public class Player { public void StartBattle(Enemy enemy) { enemy.OnDead += this.OnEnemyDead; } } イベントハンドラのメソッド名の命名例をObserverに登録するメソッドに適用 public class Enemy { public IObservable<Unit> OnDead { get; } } public class Player { public void StartBattle(Enemy enemy) { enemy.OnDead.Subscribe(_ => this.OnEnemyDead()); } } 例文で検証 上記の考え方で書いたプログラムが「英語の自然言語に近いプログラム」となり得るのかどうか、例文を作って検証してみます。 Rx命名案検証用の例文 public class Enemy { // (1) private readonly Subject<Unit> onDead = new(); public IObservable<Unit> OnDead => onDead; public void Die() { // (2) this.onDead.OnNext(Unit.Default); } } public class Player { public void SratBattle(Enemy enemy) { // (3) (4) enemy.OnDead.Subscribe(onNext: _ => this.OnEnemyDead()); } private void OnEnemyDead() { // 敵死亡時の処理 } } (1)〜(4)それぞれのコードを英文になるように組み立て、翻訳サイトDeepLで翻訳して意味の通る日本語となるか検証します。 (1) Subject<Unit> onDead まずは(1)の部分を英文にしてみます。 RxをPub/Subパターンで捉えてみるの解釈では、Subjectのインスタンス生成は、Subjectの内部ObserverがSubject利用クラスを観測開始することを意味するのでした。 この役割を示すような英文を考えると、以下のようになりました。 A subject of onDead is messages published on the enemy’s being dead. →onDeadの観測テーマは、敵が死んだことで発行されるメッセージです。 翻訳サイトの翻訳結果から、Subjectの部分のみ「観測テーマ」に変更しています。 onDeadから創造されたとは思えない程長くなってしまっていますが、以下の部分以外はボイラープレートです。 onDead - フィールド名 enemy - クラス名 being dead - イベント発火タイミング (2) onDead.OnNext() 続いて(2)の方です。 自然な文となるように、onDead(Subject)をJohnとします。 John, do the process for on next message's having been published. →ジョンさん、次のメッセージが公開された時の処理をしてください。 OnNext()は、上記の文章の中で一番重要な部分のみを抜き出しているという考えです。 (3) enemy.OnDead.Subscribe() enemy.OnDeadとSubscribe()に分解して考えます。 enemy.OnDead RxをPub/Subパターンで捉えてみるの解釈ではObservableはBrokerとなるので、それを説明する文を作りました。 An observable one called OnDead is a broker for messages published on the enemy’s being dead. →OnDeadと呼ばれる観察可能なものは、敵が死んだ時に発行されるメッセージの仲介者です。 (1)と同じで、以下の部分以外はボイラープレートです。 OnDead - プロパティ名 enemy - インスタンス名 being dead - イベント発火タイミング Subscribe() RxをPub/Subパターンで捉えてみるの解釈では、Subscribe()はObservableに対して購読の手続きを行うように命令しているのでした。 これを説明するように英文を組み立てます。 自然な文となるように、今度はJaneにOnDead(=Observable)になってもらいます。 Jane, start procedure for subscribing to messages. →ジェーンさん、メッセージを購読する手続きを開始します。 命令文として訳してほしいところですが、残念ながら平叙文になってしまいました。 とりあえずそのまま貼っておきます。 (4) Subscribe(onNext: _ => this.OnEnemyDead()) Subscribe()の引数であるonNext: _ => this.EnemyDead()の部分はこのようになります。 Playerの代表はJackに務めてもらいます。 Jack, on next message's having been published, do the process on enemy's being dead. →ジャック、次のメッセージが公開されたら、敵が死んだときの処理をしてください。 こちらも以下の部分以外はボイラープレートです。 enemy - インスタンス名 being dead - イベント発火タイミング 文を繋げると… (1)〜(4)の英文とその訳を繋げました。 便宜上付与したJohn等の名前はメンバ名に戻し、理解しやすいように少し日本語訳に手を入れました。 A subject of onDead is messages published on the enemy’s being dead. "onDead, do the process for on next message's having been published. " An observable one called OnDead is a broker for messages published on the enemy’s being dead. "OnDead, start procedure for subscribing to messages." "Player, on next message's having been published, do the process on enemy's being dead." onDeadの観測テーマは、敵が死ぬことで発行されるメッセージです。 「onDead、次のメッセージが公開された時の処理をしてください。」 OnDeadと呼ばれる観察可能なものは、敵が死んだ時に発行されるメッセージの仲介者です。 「OnDead、メッセージを購読する手続きを開始してください。」 「Player、次のメッセージが公開されたら、敵が死んだときの処理をしてください。」 これだけで仕様を説明する文章を作ることができました。 おわりに 補完しているしている語句が多いので、こじつけのように感じられるかもしれません。 しかし、補完している部分は、クラス名やインスタンス名、イベントの内容が変わってもそのまま使えます。 このため、場面によって変わる部分のみコードに記載し、共通部分は脳内でテンプレート化しておいて随時脳内補完するという整理も可能なのではないかと思います。 この脳内補完と翻訳がスムーズに行えるようになると、プログラムを理解する速度が速くなると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumの便利な使い方(C#)

はじめに C#でEnumを使うとき、int値ではなく、文字に変換して使いたいということがあります。 これをどのように実現するか、一つの実例を書かせていただきます。 またほかにも便利になりそうなメソッドも記載しています。 ちなみにここではサンプルなのでコンソールで作っています。 enumサンプルコード とりあえず、こんな感じのenumで試していきます。 public enum SampleEnum { Tokyo, Nagoya, Sapporo, Osaka } ありがちな方法 このままenum値を表示させるとこんな感じ static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.ReadLine(); } 結果 勉強をはじめたばかりだと、enumは分かるけど、実際にView(Window)に反映させたり、ComboBoxのItesSourceに使うときには、文字で表現したいけど、どうしたらいいかわからないということがあります。自分もそうでした。 たとえば、こんな感じでいちいち文字に変換したりとか... static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値を変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.ReadLine(); } /// <summary> /// enum値を日本語に変換します /// </summary> /// <param name="sampleEnum"></param> /// <returns></returns> static string GetName(SampleEnum sampleEnum) { switch (sampleEnum) { case SampleEnum.Tokyo: return "東京"; case SampleEnum.Nagoya: return "名古屋"; case SampleEnum.Sapporo: return "札幌"; case SampleEnum.Osaka: return "大阪"; default: return null; } } 結果 これでも別にいいのですが、ただ、このenumが複数のところで使われていて、変更があった時などは、面倒だったりします。 特に変換メソッドが複数のところで書かれてしまうと、エラーの原因になります。 Descriptionを使いましょう Microsoft DocumentにDescriptionAttribute クラスというものがあります このDescriptionはプロパティやイベントの説明文として使うものですが、これを読み込んで、stringに変換して使ってしまおうということです。 この辺りはググるとたくさんのサンプルが出てくるので、調べるだけでも勉強になると思います。 記事書きながら検索したらたとえばこんなのがありました Convert Enum to String in C# Enum Description to String [C#] enum値に文字列情報を紐付ける方法 で、さきほどのサンプルコードにDescriptionを付加するとこんな感じになります。 ちなみに、この後使用するメソッドがわかりやすくなるように値をセットしています using System.ComponentModel; //これが必要です public enum SampleEnum { [Description("東京")] Tokyo = 1, [Description("名古屋")] Nagoya = 3, [Description("札幌")] Sapporo = 5, [Description("大阪")] Osaka = 7 } enum用のメソッドを作る ここに紹介するメソッドは自分に使いやすいようにしたものですので、使い勝手は人によると思います。ご自身の開発に合わせていろいろと修正をしていただければと思います。 enum値からDescriptionを取得するメソッド Descriptionからenum値を取得するメソッド int値からDescriptionを取得するメソッド int値からenum値を取得するメソッド enum値のリストを取得するメソッド Descriptionのリストを取得するメソッド をヘルパークラスとしてまとめています。メソッドで型指定も行っています。 using System; using System.ComponentModel; using System.Linq; using System.Reflection; namespace EnumSample { public static class EnumHelper { /// <summary> /// 列挙体フィールドのDescriptionを取得する。 /// </summary> /// <param name="value">列挙体値</param> /// <returns>Description文字列</returns> public static string GetEnumDescriptionFromValue<T>(object value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); string description = null; if (value != null) { string strValue = value.ToString(); description = strValue; if (strValue.Length > 0) { FieldInfo fieldInfo = type.GetField(strValue); // Descriptionが複数ある場合はこちらのコードにします //var attrebutes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); //var dcs = attrebutes.Select(x => x.Description).FirstOrDefault(); //if(dcs != null) //{ // description = dcs; //} Attribute attribute = Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)); if (attribute != null) { DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attribute; description = descriptionAttribute.Description; } } } return description; } /// <summary> /// Descriptionから列挙体の値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="description"></param> /// <returns></returns> public static T GetEnumValueFromDescription<T>(string description) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); FieldInfo[] fields = type.GetFields(); var field = fields .SelectMany(f => f.GetCustomAttributes(typeof(DescriptionAttribute), false), (f, a) => new { Field = f, Att = a }) .Where(a => ((DescriptionAttribute)a.Att) .Description == description).SingleOrDefault(); return field == null ? default(T) : (T)field.Field.GetRawConstantValue(); } /// <summary> /// int値からDescriptionを取得する /// 逆は(int)enum value で取得できます /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string GetEnumDescriptionFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); var enumValue = (T)Enum.ToObject(type, value); return GetEnumDescriptionFromValue<T>(enumValue); } /// <summary> /// int値からenum value値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T GetEnumValueFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); return (T)Enum.ToObject(type, value); } /// <summary> /// enumの値リストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<T> GetEnumList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<T> values = new List<T>(); foreach (T value in Enum.GetValues(type)) { values.Add(value); } return values; } /// <summary> /// enumのDescripntionリストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<string> GetEnumDescriptionList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<string> descriptions = new List<string>(); foreach (T value in Enum.GetValues(type)) { var description = GetEnumDescriptionFromValue<T>(value); descriptions.Add(description); } return descriptions; } } } メソッドを使った例 実際にメソッドを使って、コーディングしてみます static void Main(string[] args) { Console.WriteLine(); Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値をCase変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromValueを使ってTokyoのDescriptionを取得"); var tokyo = EnumHelper.GetEnumDescriptionFromValue<SampleEnum>(SampleEnum.Tokyo); Console.WriteLine(tokyo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得"); var nagoya = EnumHelper.GetEnumValueFromDescription<SampleEnum>("名古屋"); Console.WriteLine(nagoya); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromIntを使ってint値からDescriptionを取得"); var sapporo = EnumHelper.GetEnumDescriptionFromInt<SampleEnum>(5); Console.WriteLine(sapporo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromIntを使ってint値からenum値を取得"); var osaka = EnumHelper.GetEnumValueFromInt<SampleEnum>(7); Console.WriteLine(osaka); Console.WriteLine(); Console.WriteLine("GetEnumListを使ってenumの値リストを取得"); var enums = EnumHelper.GetEnumList<SampleEnum>(); foreach(var e in enums) Console.WriteLine(e); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionListを使ってDescriptionリストを取得"); var descriptions = EnumHelper.GetEnumDescriptionList<SampleEnum>(); foreach (var d in descriptions) Console.WriteLine(d); Console.WriteLine(); Console.ReadLine(); } 結果がこんな感じでうまく動いてくれているのがわかります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumの便利に使うメソッド(C#)

はじめに C#でEnumを使うとき、int値ではなく、文字に変換して使いたいということがあります。 これをどのように実現するか、一つの実例を書かせていただきます。 またほかにも便利になりそうなメソッドも記載しています。 ちなみにここではサンプルなのでコンソールで作っています。 enumサンプルコード とりあえず、こんな感じのenumで試していきます。 public enum SampleEnum { Tokyo, Nagoya, Sapporo, Osaka } ありがちな方法 このままenum値を表示させるとこんな感じ static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.ReadLine(); } 結果 勉強をはじめたばかりだと、enumは分かるけど、実際にView(Window)に反映させたり、ComboBoxのItesSourceに使うときには、文字で表現したいけど、どうしたらいいかわからないということがあります。自分もそうでした。 たとえば、こんな感じでいちいち文字に変換したりとか... static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値をCase変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.ReadLine(); } /// <summary> /// enum値を日本語に変換します /// </summary> /// <param name="sampleEnum"></param> /// <returns></returns> static string GetName(SampleEnum sampleEnum) { switch (sampleEnum) { case SampleEnum.Tokyo: return "東京"; case SampleEnum.Nagoya: return "名古屋"; case SampleEnum.Sapporo: return "札幌"; case SampleEnum.Osaka: return "大阪"; default: return null; } } 結果 これでも別にいいのですが、ただ、このenumが複数のところで使われていて、変更があった時などは、面倒だったりします。 特に変換メソッドが複数のところで書かれてしまうと、エラーの原因になります。 Descriptionを使いましょう Microsoft DocumentにDescriptionAttribute クラスというものがあります このDescriptionはプロパティやイベントの説明文として使うものですが、これを読み込んで、stringに変換して使ってしまおうということです。 この辺りはググるとたくさんのサンプルが出てくるので、調べるだけでも勉強になると思います。 記事書きながら検索したらたとえばこんなのがありました Convert Enum to String in C# Enum Description to String [C#] enum値に文字列情報を紐付ける方法 で、さきほどのサンプルコードにDescriptionを付加するとこんな感じになります。 ちなみに、この後使用するメソッドがわかりやすくなるように値をセットしています using System.ComponentModel; //これが必要です public enum SampleEnum { [Description("東京")] Tokyo = 1, [Description("名古屋")] Nagoya = 3, [Description("札幌")] Sapporo = 5, [Description("大阪")] Osaka = 7 } enum用のメソッドを作る ここに紹介するメソッドは自分に使いやすいようにしたものですので、使い勝手は人によると思います。ご自身の開発に合わせていろいろと修正をしていただければと思います。 enum値からDescriptionを取得するメソッド Descriptionからenum値を取得するメソッド int値からDescriptionを取得するメソッド int値からenum値を取得するメソッド enum値のリストを取得するメソッド Descriptionのリストを取得するメソッド をヘルパークラスとしてまとめています。メソッドで型指定も行っています。 using System; using System.ComponentModel; using System.Linq; using System.Reflection; namespace EnumSample { public static class EnumHelper { /// <summary> /// 列挙体フィールドのDescriptionを取得する。 /// </summary> /// <param name="value">列挙体値</param> /// <returns>Description文字列</returns> public static string GetEnumDescriptionFromValue<T>(object value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); string description = null; if (value != null) { string strValue = value.ToString(); description = strValue; if (strValue.Length > 0) { FieldInfo fieldInfo = type.GetField(strValue); // Descriptionが複数ある場合はこちらのコードにします //var attrebutes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); //var dcs = attrebutes.Select(x => x.Description).FirstOrDefault(); //if(dcs != null) //{ // description = dcs; //} Attribute attribute = Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)); if (attribute != null) { DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attribute; description = descriptionAttribute.Description; } } } return description; } /// <summary> /// Descriptionから列挙体の値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="description"></param> /// <returns></returns> public static T GetEnumValueFromDescription<T>(string description) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); FieldInfo[] fields = type.GetFields(); var field = fields .SelectMany(f => f.GetCustomAttributes(typeof(DescriptionAttribute), false), (f, a) => new { Field = f, Att = a }) .Where(a => ((DescriptionAttribute)a.Att) .Description == description).SingleOrDefault(); return field == null ? default(T) : (T)field.Field.GetRawConstantValue(); } /// <summary> /// int値からDescriptionを取得する /// 逆は(int)enum value で取得できます /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string GetEnumDescriptionFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); var enumValue = (T)Enum.ToObject(type, value); return GetEnumDescriptionFromValue<T>(enumValue); } /// <summary> /// int値からenum value値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T GetEnumValueFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); return (T)Enum.ToObject(type, value); } /// <summary> /// enumの値リストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<T> GetEnumList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<T> values = new List<T>(); foreach (T value in Enum.GetValues(type)) { values.Add(value); } return values; } /// <summary> /// enumのDescripntionリストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<string> GetEnumDescriptionList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<string> descriptions = new List<string>(); foreach (T value in Enum.GetValues(type)) { var description = GetEnumDescriptionFromValue<T>(value); descriptions.Add(description); } return descriptions; } } } メソッドを使った例 実際にメソッドを使って、コーディングしてみます static void Main(string[] args) { Console.WriteLine(); Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値をCase変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromValueを使ってTokyoのDescriptionを取得"); var tokyo = EnumHelper.GetEnumDescriptionFromValue<SampleEnum>(SampleEnum.Tokyo); Console.WriteLine(tokyo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得"); var nagoya = EnumHelper.GetEnumValueFromDescription<SampleEnum>("名古屋"); Console.WriteLine(nagoya); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromIntを使ってint値からDescriptionを取得"); var sapporo = EnumHelper.GetEnumDescriptionFromInt<SampleEnum>(5); Console.WriteLine(sapporo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromIntを使ってint値からenum値を取得"); var osaka = EnumHelper.GetEnumValueFromInt<SampleEnum>(7); Console.WriteLine(osaka); Console.WriteLine(); Console.WriteLine("GetEnumListを使ってenumの値リストを取得"); var enums = EnumHelper.GetEnumList<SampleEnum>(); foreach(var e in enums) Console.WriteLine(e); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionListを使ってDescriptionリストを取得"); var descriptions = EnumHelper.GetEnumDescriptionList<SampleEnum>(); foreach (var d in descriptions) Console.WriteLine(d); Console.WriteLine(); Console.ReadLine(); } 結果がこんな感じでうまく動いてくれているのがわかります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumを便利に使うメソッド(C#)

はじめに C#でEnumを使うとき、int値ではなく、文字に変換して使いたいということがあります。 これをどのように実現するか、一つの実例を書かせていただきます。 またほかにも便利になりそうなメソッドも記載しています。 ちなみにここではサンプルなのでコンソールで作っています。 コメントをいただいて、拡張メソッドのコードの追記をしました(2021/7/25)。 コメントいただいた@albireo ありがとうございます。 enumサンプルコード とりあえず、こんな感じのenumで試していきます。 public enum SampleEnum { Tokyo, Nagoya, Sapporo, Osaka } ありがちな方法 このままenum値を表示させるとこんな感じ static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.ReadLine(); } 結果 勉強をはじめたばかりだと、enumは分かるけど、実際にView(Window)に反映させたり、ComboBoxのItesSourceに使うときには、文字で表現したいけど、どうしたらいいかわからないということがあります。自分もそうでした。 たとえば、こんな感じでいちいち文字に変換したりとか... static void Main(string[] args) { Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値をCase変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.ReadLine(); } /// <summary> /// enum値を日本語に変換します /// </summary> /// <param name="sampleEnum"></param> /// <returns></returns> static string GetName(SampleEnum sampleEnum) { switch (sampleEnum) { case SampleEnum.Tokyo: return "東京"; case SampleEnum.Nagoya: return "名古屋"; case SampleEnum.Sapporo: return "札幌"; case SampleEnum.Osaka: return "大阪"; default: return null; } } 結果 これでも別にいいのですが、ただ、このenumが複数のところで使われていて、変更があった時などは、面倒だったりします。 特に変換メソッドが複数のところで書かれてしまうと、エラーの原因になります。 Descriptionを使いましょう Microsoft DocumentにDescriptionAttribute クラスというものがあります このDescriptionはプロパティやイベントの説明文として使うものですが、これを読み込んで、stringに変換して使ってしまおうということです。 この辺りはググるとたくさんのサンプルが出てくるので、調べるだけでも勉強になると思います。 記事書きながら検索したらたとえばこんなのがありました Convert Enum to String in C# Enum Description to String [C#] enum値に文字列情報を紐付ける方法 で、さきほどのサンプルコードにDescriptionを付加するとこんな感じになります。 ちなみに、この後使用するメソッドがわかりやすくなるように値をセットしています using System.ComponentModel; //これが必要です public enum SampleEnum { [Description("東京")] Tokyo = 1, [Description("名古屋")] Nagoya = 3, [Description("札幌")] Sapporo = 5, [Description("大阪")] Osaka = 7 } enum用のメソッドを作る ここに紹介するメソッドは自分に使いやすいようにしたものですので、使い勝手は人によると思います。ご自身の開発に合わせていろいろと修正をしていただければと思います。 enum値からDescriptionを取得するメソッド Descriptionからenum値を取得するメソッド int値からDescriptionを取得するメソッド int値からenum値を取得するメソッド enum値のリストを取得するメソッド Descriptionのリストを取得するメソッド をヘルパークラスとしてまとめています。メソッドで型指定も行っています。 using System; using System.ComponentModel; using System.Linq; using System.Reflection; namespace EnumSample { public static class EnumHelper { /// <summary> /// 列挙体フィールドのDescriptionを取得する。 /// </summary> /// <param name="value">列挙体値</param> /// <returns>Description文字列</returns> public static string GetEnumDescriptionFromValue<T>(object value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); string description = null; if (value != null) { string strValue = value.ToString(); description = strValue; if (strValue.Length > 0) { FieldInfo fieldInfo = type.GetField(strValue); // Descriptionが複数ある場合はこちらのコードにします //var attrebutes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); //var dcs = attrebutes.Select(x => x.Description).FirstOrDefault(); //if(dcs != null) //{ // description = dcs; //} Attribute attribute = Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)); if (attribute != null) { DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attribute; description = descriptionAttribute.Description; } } } return description; } /// <summary> /// Descriptionから列挙体の値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="description"></param> /// <returns></returns> public static T GetEnumValueFromDescription<T>(string description) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); FieldInfo[] fields = type.GetFields(); var field = fields .SelectMany(f => f.GetCustomAttributes(typeof(DescriptionAttribute), false), (f, a) => new { Field = f, Att = a }) .Where(a => ((DescriptionAttribute)a.Att) .Description == description).SingleOrDefault(); return field == null ? default(T) : (T)field.Field.GetRawConstantValue(); } /// <summary> /// int値からDescriptionを取得する /// 逆は(int)enum value で取得できます /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string GetEnumDescriptionFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); var enumValue = (T)Enum.ToObject(type, value); return GetEnumDescriptionFromValue<T>(enumValue); } /// <summary> /// int値からenum value値を取得する /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T GetEnumValueFromInt<T>(int value) { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); return (T)Enum.ToObject(type, value); } /// <summary> /// enumの値リストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<T> GetEnumList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<T> values = new List<T>(); foreach (T value in Enum.GetValues(type)) { values.Add(value); } return values; } /// <summary> /// enumのDescripntionリストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<string> GetEnumDescriptionList<T>() { var type = typeof(T); if (!type.IsEnum) throw new ArgumentException(); List<string> descriptions = new List<string>(); foreach (T value in Enum.GetValues(type)) { var description = GetEnumDescriptionFromValue<T>(value); descriptions.Add(description); } return descriptions; } } } 確認用コード 実際にメソッドを使って、コーディングしてみます static void Main(string[] args) { Console.WriteLine(); Console.WriteLine("enum値のままで表示させた場合"); Console.WriteLine(SampleEnum.Tokyo); Console.WriteLine(SampleEnum.Nagoya); Console.WriteLine(SampleEnum.Sapporo); Console.WriteLine(SampleEnum.Osaka); Console.WriteLine(); Console.WriteLine("enum値をCase変換してみた"); Console.WriteLine(GetName(SampleEnum.Tokyo)); Console.WriteLine(GetName(SampleEnum.Nagoya)); Console.WriteLine(GetName(SampleEnum.Sapporo)); Console.WriteLine(GetName(SampleEnum.Osaka)); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromValueを使ってTokyoのDescriptionを取得"); var tokyo = EnumHelper.GetEnumDescriptionFromValue<SampleEnum>(SampleEnum.Tokyo); Console.WriteLine(tokyo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得"); var nagoya = EnumHelper.GetEnumValueFromDescription<SampleEnum>("名古屋"); Console.WriteLine(nagoya); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionFromIntを使ってint値からDescriptionを取得"); var sapporo = EnumHelper.GetEnumDescriptionFromInt<SampleEnum>(5); Console.WriteLine(sapporo); Console.WriteLine(); Console.WriteLine("GetEnumValueFromIntを使ってint値からenum値を取得"); var osaka = EnumHelper.GetEnumValueFromInt<SampleEnum>(7); Console.WriteLine(osaka); Console.WriteLine(); Console.WriteLine("GetEnumListを使ってenumの値リストを取得"); var enums = EnumHelper.GetEnumList<SampleEnum>(); foreach(var e in enums) Console.WriteLine(e); Console.WriteLine(); Console.WriteLine("GetEnumDescriptionListを使ってDescriptionリストを取得"); var descriptions = EnumHelper.GetEnumDescriptionList<SampleEnum>(); foreach (var d in descriptions) Console.WriteLine(d); Console.WriteLine(); Console.ReadLine(); } 結果がこんな感じでうまく動いてくれているのがわかります 追記 コード大幅変更しました @albireoさんにコメントでご指摘していただいたwhere Tつけて、さらに拡張メソッドを作ってみました。 コメントがとても勉強になり、大感謝です! GetEnumDescriptionFromValue -> 拡張メソッド(EnumExtention) public static class EnumExtention { /// <summary> /// EnumのValueからDescriptionを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string GetDescriptionFromValue<T>(this T value) where T : Enum //where T : Enum とすることで Tがenumでない場合はコンパイル時にエラーにしてくれる { //valueはenum型確定なので空文字が返ることはない string strValue = value.ToString(); var description = typeof(T).GetField(strValue) //FiledInfoを取得 .GetCustomAttributes(typeof(DescriptionAttribute), false) //DescriptionAttributeのリストを取得 .Cast<DescriptionAttribute>() //DescriptionAttributeにキャスト .FirstOrDefault() //最初の一つを取得、なければnull ?.Description; //DescriptionAttributeがあればDescriptionを、なければnullを返す return description ?? strValue; //descriptionがnullならstrValueを返す } } コメントで書いていただいたまんまを拡張メソッドにしただけですww で、これをもとに、拡張メソッドを勉強したので、ほかのものも書き直したのが以下となります。 GetEnumValueFromDescription -> 拡張メソッド(StringExtention) public static class StringExtention { /// <summary> /// EnumのDescriptionからValueを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="description"></param> /// <returns></returns> public static T GetEnumValueFromDescription<T>(this string description) where T : Enum { var value = typeof(T).GetFields() .SelectMany(x => x.GetCustomAttributes(typeof(DescriptionAttribute), false), (f, a) => new { field = f, attribute = a }) .Where(x => ((DescriptionAttribute)x.attribute).Description == description) .FirstOrDefault() ?.field.GetRawConstantValue(); //// 値が見つからない場合にエラーとする場合はこちら //return (T)(value ?? throw new ArgumentNullException()); return (T)(value ?? default(T)); } } GetEnumDescriptionFromInt, GetEnumValueFromInt -> 拡張メソッド(IntExtention) public static class IntExtention { /// <summary> /// int値からEnumのDescription取得 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string GetEnumDescriptionFromInt<T>(this int value) where T : Enum { var enumValue = (T)Enum.ToObject(typeof(T), value); return enumValue.GetDescriptionFromValue(); } /// <summary> /// int値からEnumのValueを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T GetEnumValueFromInt<T>(this int value) where T : Enum { return (T)Enum.ToObject(typeof(T), value); } } var enumValue = (T)value;がコンパイルエラー(CS0030)となってしまい、うまくいかなかったので、(T)Enum.ToObject(typeof(T), value);のままとしています。 GetEnumList, GetEnumDescriptionList -> 拡張メソッド(TypeExtention) public static class TypeExtention { /// <summary> /// enumの値リストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<T> GetEnumList<T>(this Type type) where T : Enum { return Enum.GetValues(typeof(T)).Cast<T>().ToList(); } /// <summary> /// enumのDescripntionリストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static IEnumerable<string> GetEnumDescriptionEnumerable<T>(this Type type) where T : Enum { foreach (T value in Enum.GetValues(typeof(T))) yield return value.GetDescriptionFromValue(); } /// <summary> /// enumのDescripntionリストを取得 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static List<string> GetEnumDescriptionList<T>(this Type type) where T : Enum { List<string> descriptionList = new List<string>(); foreach (T value in Enum.GetValues(typeof(T))) descriptionList.Add(value.GetDescriptionFromValue()); return descriptionList; } } 確認用コード static void Test2() { Console.WriteLine("EnumExtention GetDescriptionFromValueを使ってTokyoのDescriptionを取得"); Console.WriteLine(SampleEnum.Tokyo.GetDescriptionFromValue()); Console.WriteLine(); Console.WriteLine("StringExtention GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得"); Console.WriteLine("名古屋".GetEnumValueFromDescription<SampleEnum>()); Console.WriteLine(); Console.WriteLine("IntExtention GetEnumDescriptionFromIntを使ってint値からDescriptionを取得"); var sapporo = ((int)5).GetEnumDescriptionFromInt<SampleEnum>(); Console.WriteLine(sapporo); Console.WriteLine(); Console.WriteLine("IntExtention GetEnumValueFromIntを使ってint値からenum値を取得"); var osaka = ((int)7).GetEnumValueFromInt<SampleEnum>(); Console.WriteLine(osaka); Console.WriteLine(); Console.WriteLine("TypeExtention GetEnumListを使ってenumの値リストを取得"); var enumValues = typeof(SampleEnum).GetEnumList<SampleEnum>(); foreach (var e in enumValues) Console.WriteLine(e); Console.WriteLine(); Console.WriteLine("TypeExtention GetEnumDescriptionListを使ってDescriptionリストを取得"); var enumDescriptions = typeof(SampleEnum).GetEnumDescriptionList<SampleEnum>(); foreach (var e in enumDescriptions) Console.WriteLine(e); Console.WriteLine(); Console.WriteLine("TypeExtention GetEnumDescriptionEnumerableを使ってDescriptionリストを取得"); var enumDescriptionsEnumerable = typeof(SampleEnum).GetEnumDescriptionEnumerable<SampleEnum>(); foreach (var e in enumDescriptionsEnumerable) Console.WriteLine(e); Console.ReadLine(); } 結果 拡張メソッドを自分ではじめて作りました。 これであっているのか自信がありませんが、とてもよい勉強となりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(C#) PDFを読み込んで、Stream内をすべて別ファイルに書き出す、サンプルソース

(C#) PDFを読み込んで、 Stream内をすべて別ファイルに書き出す、サンプルソース 1) 引数で、PDFファイル名を渡す 2) PDFを、Byte列に、一度に読み込み 3) PDF内、Byte列を順次上から下までナメて、stream~endstreamの中身を、ファイルに書き出し。 4) "stream_out_" + 連番 + ".bin" 実行例 ソース pdf_stream_out.cs //c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe pdf_stream_out.cs using System; using System.IO; class pdf_stream_out { //*1 static void Main(string[] args) { //*2 string p_fn = @"" + args[0]; byte[] data = File.ReadAllBytes(p_fn); int strmCnt = 1; int ARCN = 2; //array_count string[] st = new string[ARCN]; st[0] = ">stream"; st[1] = "endstream"; byte[][] d = new byte[ARCN][]; for(int i=0; i<ARCN; ++i) d[i] = System.Text.Encoding.ASCII.GetBytes(st[i]); byte[][] dm = new byte[ARCN][]; for(int i=0; i<ARCN; ++i) dm[i] = new byte[d[i].Length]; int stream_stt = 0; int stream_size = 0; for(int i=0;i<data.Length-st[1].Length;i++){ //-st[1]は、一番長い配列 for(int j=0; j<ARCN; ++j) Array.Copy(data,i,dm[j],0,d[j].Length); bool[] aEq = new bool[ARCN]; for(int j=0; j<ARCN; ++j){ aEq[j] = System.Linq.Enumerable.SequenceEqual(d[j], dm[j]); if(aEq[j]){ //">stream" if(j==0){ stream_stt = (int)(i+(int)d[j].Length+2); Console.WriteLine("stream_start= " + stream_stt); } //"endstream" if(j==1){ stream_size = (int)((int)i-3) - stream_stt +1; Console.WriteLine("stream_size= " + stream_size); byte[] data_out = new byte[stream_size]; Array.Copy(data,stream_stt,data_out,0,stream_size); File.WriteAllBytes("stream_out_" +strmCnt+ ".bin", data_out); strmCnt++; } string tx = System.Text.Encoding.ASCII.GetString(dm[j]); Console.Write(i + "_" + tx + ":"); Console.WriteLine("_"); } } } } //*2 } //*1 書き出し結果 参考 (C#) PDFを読み込んで、埋め込まれた画像をJPGファイルとして書き出す(DCTDecodeのStream内をママ書き出しているだけなのでかなり雑) https://qiita.com/santarou6/items/5f011e266929558ede34 C# JpgからPDFへ変換(1Jpgファイルを1ページとしたPDFファイルの作成) https://qiita.com/santarou6/items/ff24500c13d05b12a940
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EFCoreのデータ取得のパフォーマンスでドハマりした話

むしゃくしゃしたので若干深夜テンション込みで書き殴り。 とっちらかってるし見落しとか何とかいろいろあるはず。 結論 ・データ取得時 (特に編集をしない場合) はAsNoTracking()を呼んでおく ・ThenInclude()は可能であれば使わない ・使う必要がある場合は上手く実装できないとThenInclude()を使うのが(多分)一番速い ・EFCoreなんて知らんとばかりにゴリゴリSQL発行するのがいいのかもしれない(試してない) 追記 ・ORDER BYがボトルネックになってたのでInclude()時のORDER BYについて調べてたらこんなのもあった → https://github.com/dotnet/efcore/issues/19828 ・いっそ6.0リリース待つのが正解かもしれない 前提 ・既存の管理ツールが合わなかったので自前でBMSの管理ツール作ろうとした。 ・フォルダ数が1万over,BMSファイル数が6万over ・開発は.NET5 WPF EFCore5.0 SQLServer2019 ・BMSの特性上ファイルはフォルダ単位で管理 ・ファイルが保管されているフォルダが入っているディレクトリをルートとする ・ルートフォルダは管理のために階層化する データの形式 ・ルートフォルダ-BMSフォルダ-BMSファイルで階層にしてた。 起きたこと ファイルの読み込み→DBへの書き込みが恙無く完了し、いざDBからのデータ呼び出しを実行するとクッソ遅い。(30秒以上かかった) 最初のコード using (var con = new Context()) { var root = con.Root .Include(d => d.Children) .Include(d => d.Folders) .ThenInclude(d => f.Files).ToArray(); } とりあえずログ確認するとMicrosoft.EntityFrameworkCore.ChangeTrackingから始まるログが大量に流れてる → とりあえずAsNoTracking()を呼び出すように変更。最短10秒ちょっとまで縮まる。 AsNoTracking using (var con = new Context()) { var root = con.Root .Include(d => d.Children) .Include(d => d.Folders) .ThenInclude(d => f.Files) .AsNoTracking().ToArray(); } デバッグ実行するとToArray()でめちゃくちゃ時間がかかっていた。いくらInclude()・ThenInclude()があるとはいえ、内容的には単純なJOIN,LEFT JOINで取得できるような内容に時間かかりすぎな気がすると思ってSQL確認してみた。 SELECT [r].[ID], [r].[ParentRootID], [r].[Path], [r0].[ID], [r0].[ParentRootID], [r0].[Path], [t].[ID], [t].[Artist], [t].[Path], [t].[RootID], [t].[Title], [t].[ID0], [t].[Artist0], [t].[FolderID], [t].[MD5], [t].[Path0], [t].[Title0] FROM [Root] AS [r] LEFT JOIN [Root] AS [r0] ON [r].[ID] = [r0].[ParentRootID] LEFT JOIN ( SELECT [f].[ID], [f].[Artist], [f].[Path], [f].[RootID], [f].[Title], [f0].[ID] AS [ID0], [f0].[Artist] AS [Artist0], [f0].[FolderID], [f0].[MD5], [f0].[Path] AS [Path0], [f0].[Title] AS [Title0] FROM [Folder] AS [f] LEFT JOIN [File] AS [f0] ON [f].[ID] = [f0].[FolderID] ) AS [t] ON [r].[ID] = [t].[RootID] ORDER BY [r].[ID], [r0].[ID], [t].[ID], [t].[ID0] ……なんでわざわざサブクエリ? 書きながら見返すと足引っ張ってるの完全にORDER BYだけどもういいや 試しにThenInclude()を外して実行してみると綺麗にLEFT JOINだけになって結果もすぐに返ってくるようになった。 Fileがロードされない状態になるので求めていたデータにはならないのだけれども。 あとはInclude()で階層化されたデータをロードしてもThenInclude()と等価になるっぽい。よくよく考えれば当然ではあるのだけれども。 こんな感じ using (var con = new Context()) { var root = con.Root .Include(r => r.Children) .Include(r => r.Folders) .Include($"{nameof(Root.Folders)}.{nameof(Folder.Files)}") .AsNoTracking().ToArray(); } というわけで先にFileテーブル全部読んでそれをFolderに投げ込むとか、Folder毎にクエリ発行するとかやってみたけど全滅。そりゃそうだ。万件オーダーのループに万件オーダーのループがネストされるんだから。 もうちょいなんとかならんかといろいろ調べる → こんなの見つける https://github.com/dotnet/efcore/issues/17622 → 諦める (イマココ) ・ちなみに FolderにFileをIncludeして実行したら即返ってきたのでデータ件数による問題ではないのは確実なはず。 書きながら別案も浮かんだので気力があれば試してみようと思う。どちらにしろ万件オーダーのループなので正直望み薄な気がする ・一応改善はできた using (var con = new Context()) { var folders = con.BmsFolders .Include(f => f.Files) .AsNoTracking().ToArray(); var allRoots = con.RootDirectories //.Include(r => r.Children) //.Include(d => d.Folders) // .ThenInclude(f => f.Files) .AsNoTracking().ToArray(); // これでも大分マシな速度は出た //foreach (var root in allRoots) //{ // root.Folders = folders.Where(f => f.RootID == root.ID).ToList(); //} // ↑に比べてループ部分は10分の1ぐらいで終わった foreach (var folder in folders.GroupBy(f => f.RootID)) { var root = allRoots.FirstOrDefault(r => r.ID == folder.Key); root.Folders = folder.ToList(); } } 車輪の再発明感がなくもない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む