20211201のC#に関する記事は11件です。

.NETでの暗号化について

.NETでの暗号化について Microsoftでは以下のように暗号化を推奨している 暗号化時のキーとベクタの生成は以下が参考になる(上記ページの続きにある) 実際の参考になるコードは以下の記事が参考になった https://qiita.com/tera1707/items/a0dc441a7a432433d84d 暗号化時のbyte桁数は規定値では16の倍数になる 以下のURLのサンプルコード参照 https://atmarkit.itmedia.co.jp/ait/articles/1709/06/news020.html 実際には0~15文字をEncoding.UTF8.GetBytes(string)した時には16バイトになる そこから16~31文字は32バイト、32~47文字は48バイト...という感じになる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラム初心者勉強会 9回目 プログレスバーの実装 -C# Windowsフォーム入門

目次 1. 前回リンク 2. 次回リンク 3. やったこと 4. 配信時の録画 5. 今回学んだこと 6. プログレスバーの動作確認 7. ダイアログ上でプログレスバーを動かす 8. 各イベントの順番について 9. 振り返り 10. 次回予告 1. 前回リンク 2. 次回リンク まだ 3. やったこと プログレスバーの実装を学んだよ 〇 ステータスバーにプログレスバーを表示させるやりかた 〇 プログレスバーをダイアログに表示させるやりかた ・ Tickイベント 4. 配信時の録画 5. 今回学んだこと プログレスバーの設置がこんなに簡単な事、そして制御がこんなにややこしいことを初めて知った。勉強してよかった 6. プログレスバーの動作確認 6.1 ツールバーにプログレスバーを表示させる場合  1. デザイン画面に「toolStrip」をDrag&Dropする  2. 配置したtoolStripを「ProgressBar」に設定する  3. 後はProgressBar化したtoolStripのプロパティ「value」「Minimum」「Maximum」を編集して、プログレスバーを自由に動かす 6.2 プログレスバーを画面にそのまま配置する  1. デザイン画面に「ProgressBar」をDrag&Dropする  2. 後はProgressBarのプロパティ「value」「Minimum」「Maximum」を編集して、プログレスバーを自由に動かす 6.3 プログレスバーの動作確認  1. Minimum=0、Maximum=100、value=0とする  2. デザイン画面にボタンを1つ配置し、そのボタンが押されるたびにvalueが10加算されるようにする  3. ボタンを押した分プログレスバーが進むことを確認する  注意: Maxよりもvalue値が大きくなるとエラーになる。  ボタンを押すごとにプログレスバーが進むボタンイベントハンドラ関数 private void button1_Click(object sender, EventArgs e) { // valueがMaxの100に到達したら0に戻る if(this.toolStripProgressBar1.Value == 100) { this.toolStripProgressBar1.Value = 0; } else { this.toolStripProgressBar1.Value += 10; } } 4. 配信時の録画の配信動画リンク、25:25から観ればボタンの挙動が分かります。 7. ダイアログ上でプログレスバーを動かす 7.1 やりたいこと フォーム画面に配置されたボタン(Button2)を押す → ダイアログが表示される → 表示されたダイアログ上でプログレスバーが自動で進む 実装するプログレスバーは、Max値が100で、1秒間にValue+=10する。 実現方法にはモーダルダイアログを開く場合、モーダレスダイアログを開く場合で方法が分かれる。 今回はモーダレスダイアログを使用した場合を記載する。 7.2 モーダレスダイアログ上にプログレスバーを配置する 7.2.1 Thread.Sleepを使用して1秒ごとの処理を実施する場合 7.2.2章記載のTickイベントを使う方が絶対に良いです。こちらは参考程度に読んでください。 プログレスバーの動作と、Form画面上の動作を非同期にすることで、ダイアログを開いた直後からプログレスバーの処理を実施できる。  Form画面用コード(Form1.cs) using System using System.Windows.Forms; namespace MenuStudy2 { public partial class Form1 : Form { // ...省略 private void button2_Click(object sender, EventArgs e) { DialogTest Dialog = new DialogTest(); Dialog.Owner = this; Dialog.Show(); Dialog.ProgressBareMove(); } } // ...省略  モーダレスダイアログ用コード(DiagTest.cs) using System using System.Windows.Forms; using System.Threading; namespace DialogTest_Progress { public partial class DialogTest : Form { // ...省略 //プログレスバーを1秒に10進める関数 public void ProgressBarMove() { for(int timer=0;timer<10;timer++) { // valueがMaxの100に到達したら0に戻る if(this.toolStripProgressBar1.Value == 100) { this.toolStripProgressBar1.Value = 0; } else { this.toolStripProgressBar1.Value += 10; } Thread.Sleep(1000); } } // ...省略 もしモーダルダイアログでこの内容のコードを動かすと、バーがうまく動いてくれないよ。 ※ 4. 配信時の録画の配信動画リンク、57:35から観れば失敗時の挙動が分かります。 ※ 4. 配信時の録画の配信動画リンク、01:12:20から観れば成功時の挙動が分かります。 このようにうまく動作しましたが、1点ダメなところがあります。それは「キャンセルボタンを実装できない」ことです。 Thread.Sleep処理時はスレッド処理がすべて停止します。今回モーダレスダイアログの処理がForループする度に1秒間停止する為、ボタンを配置しても画面上に反映される時間が極端に短くなります。 そして、人がボタンを押すことが実質不可能となります。 ※ 4. 配信時の録画の配信動画リンク、01:34:45から観れば失敗時の挙動が分かります。 そのため、繰り返しの動作を行わせる場合は、7.2.2章のTickイベントを使用すべきです。 7.3 モーダルダイアログ上にプログレスバーを配置する場合(Tickイベントを使用した場合)  1. Timer.Tickイベントを使用し、1秒間ごとにプログレスバーを移動させる。 デザイン画面で「Timer」をDrag&Dropし、Intervalプロパティを1000にする。  2. モーダルダイアログ上のShowイベント登録用関数内でTimerを開始させる。  3. Tickイベント用ハンドラ関数内で、プログレスバーを進める処理を記載  4. キャンセルボタンも実装し、キャンセルボタンを押せばダイアログが閉じるようにする  モーダレスダイアログ用コード(DiagTest.cs) namespace DialogTest_Progress { public partial class DialogTest : Form { public DialogTest() { InitializeComponent(); } // shownイベント用ハンドラ関数 private void DialogTest_Shown_1(object sender, EventArgs e) { //タイマー開始 timer1.Enabled = true; } // Tickイベント用ハンドラ関数 private void DialogTest_Shown(object sender, EventArgs e) { if (this.progressBar1.Value == 100) { this.progressBar1.Value = 0; } else { this.progressBar1.Value += 10; } } //キャンセルボタンのクリックイベントハンドラ関数 private void button1_Click(object sender, EventArgs e) { this.Close(); }  Form画面用コード(Form1.cs) using System; using System.Windows.Forms; using DialogTest_Progress; namespace MenuStudy2 { public partial class Form1 : Form { // ...省略 private void button2_Click(object sender, EventArgs e) { DialogTest Dialog = new DialogTest(); Dialog.Owner = this; Dialog.Show(); } } ※ 4. 配信時の録画の配信動画リンク、02:07:55から観れば成功時の挙動が分かります。 8. 各イベントの順番について 次に記載する記事が詳しく解説してくれていた。「Load」イベントよりも早く処理されるイベントがあるんだなとビックリしましたね 9. 振り返り プログレスバーの挙動実装はかなりハードル高いと思い込んでたので、こんなに簡単に実装出来る事にびっくりしたよ 10. 次回予告 次回は12月5日(日)の20時からやるよ。 次どこを勉強するかは考えておくね ニコニコ配信先 Twitch配信先 Discordリンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でYoutubeDataAPIでチャンネル名と概要を取得

C#でYoutubeDataAPIでチャンネル名と概要を取得 調べてもすぐ情報が出てこなかったのでサンプルで書いておく。 api.cs public void GetChannelDetail() { var url = "https://www.googleapis.com/youtube/v3/"; url += "channels"; url += "?part=snippet";           //とりあえずsnippetでいいらしい url += "&id=UCZf__ehlCEBPop-_sldpBUQ";   //HIKAKINさんのチャンネルID url += "&key=xxxxxxxxxxxxxxxxxxxxxxxxxx"; //自分のAPIKEY WebRequest request = WebRequest.Create(url); Stream stream = request.GetResponse().GetResponseStream(); StreamReader reader = new StreamReader(stream); var json = JObject.Parse(reader.ReadToEnd()); var channelName = json["items"][0]["snippet"]["title"].ToString(); var description = json["items"][0]["snippet"]["description"].ToString(); Console.WriteLine($"チャンネル名:{channelName}"); Console.WriteLine($"概要:{description}"); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

人工知能はひまつぶしにひつまぶしを食べるか(リファクタリング編・実践)

単体テストを行うために、インターフェースを作成したので、まずは、関連する箇所を修正する。 インターフェース 対象コード App.xaml.cs MainWindowViewModel.cs コンテナに登録するインスタンスは、すべてSingletonにしているが、これが妥当かどうかは別途検討する。 App.xaml.cs(変更前) protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<BalanceBase>(); containerRegistry.RegisterSingleton<Cola>(); containerRegistry.RegisterSingleton<Orange>(); containerRegistry.RegisterSingleton<Cyder>(); containerRegistry.RegisterSingleton<Energy>(); containerRegistry.RegisterSingleton<Coffee>(); containerRegistry.RegisterSingleton<GreenTea>(); containerRegistry.RegisterSingleton<BlackTea>(); containerRegistry.RegisterSingleton<Beer>(); } App.xaml.cs(変更後) containerRegistry.RegisterSingleton<IBalance, BalanceBase>(); containerRegistry.RegisterSingleton<IProduct,Cola>(); containerRegistry.RegisterSingleton<IProduct, Orange>(); containerRegistry.RegisterSingleton<IProduct, Cyder>(); containerRegistry.RegisterSingleton<IProduct, Energy>(); containerRegistry.RegisterSingleton<IProduct, Coffee>(); containerRegistry.RegisterSingleton<IProduct, GreenTea>(); containerRegistry.RegisterSingleton<IProduct, BlackTea>(); containerRegistry.RegisterSingleton<IProduct, Beer>(); MainWindowViewModel.csのフィールド定義(変更前) /// <summary> /// /// </summary> private BalanceBase balance; /// <summary> /// /// </summary> private Cola cola = null; /// <summary> /// /// </summary> private Orange orange = null; /// <summary> /// /// </summary> private Cyder cyder = null; /// <summary> /// /// </summary> private Energy energy = null; /// <summary> /// /// </summary> private Coffee coffee = null; /// <summary> /// /// </summary> private GreenTea greenTea = null; /// <summary> /// /// </summary> private BlackTea blackTea = null; /// <summary> /// /// </summary> private Beer beer = null; MainWindowViewModel.csのフィールド定義(変更後) /// <summary> /// BalanceBaseクラスのインターフェース /// </summary> private IBalance balance; /// <summary> /// Colaクラスのインターフェース /// </summary> private IProduct cola = null; /// <summary> /// /// </summary> private IProduct orange = null; /// <summary> /// /// </summary> private IProduct cyder = null; /// <summary> /// /// </summary> private IProduct energy = null; /// <summary> /// /// </summary> private IProduct coffee = null; /// <summary> /// /// </summary> private IProduct greenTea = null; /// <summary> /// /// </summary> private IProduct blackTea = null; /// <summary> /// /// </summary> private IProduct beer = null; MainWindowViewModel.csのコンストラクタ(変更前) // 残金計算クラス balance = containerProvider.Resolve<BalanceBase>(); MainWindowViewModel.csのコンストラクタ(変更後) // 残金計算クラス balance = containerProvider.Resolve<IBalance>();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

人工知能はひまつぶしにひつまぶしを食べるか(リファクタリング編・検討)

さて、ここからは、作成したコードの検討に移る。 まずは、画面(View、ViewModel)と機能について整理してみる。 入力 ボタンの機能 飲料を購入する 自動販売機にお金を投入する お釣りをもらう 出力 表示機能 残額を表示する 各ボタンを押した結果を表示する 動作 1.ボタンを押す。 2.画面に以下を表示する。 残額を表示する 各ボタンを押した結果を表示する 検討 メンテナンス性や再利用性、テストの容易さなどを考慮して、View、ViewModel、Productクラス、BindableBaseクラス、Colaクラスを検討したい。 ボタン用のメソッドは、ViewModelに作成している。似たような処理がたくさん記述されている。まず、思い浮かぶのは、サブルーチン(private)なメソッド)を作り、各ボタンのメソッドから呼び出すようにする。しかし、手続き型的な発想のようで気がとがめる。クラスとして外に出したいが、メッセージや残額を表示するためのプロパティにアクセスするため、外出しにはできない。メッセージを取得するメソッドを作成すれば、良いのかもしれないが、そのような方法が、良いのかどうか判断できない。お札や小銭を投入するボタンのメソッドは、コマンドパラメータとして、金額を渡せば、済むかもしれない。 検討(飲料購入ボタン) 飲料購入ボタンをクリックしたときの動作の概要は以下のとおり。 動作 1.飲料を購入できるかどうか確認する。  1.在庫を確認する。  2.自動販売機に投入されている残額が、飲料を購入できる金額を上回っているか確認する。 2.購入できた場合  1.在庫を減ずる。  2.残額を表示する。 3.メッセージを表示する。  1.飲料名を購入しました(2のとき)  2.在庫が足りません(1-2の条件を満たさないとき)  3.金額が足りません(1-1の条件を満たさないとき) ソースコードは以下のとおり。ボタンは、8個あるので、これと同様のメソッドが8個存在する。 MainWindowViewModel.csからの抜粋 private void ExecuteBuyCola() { try { // if (!cola.HasStock()) { Message = @"在庫が足りません"; return; } // if (balance.Debit(cola.Price)) { // cola.SubtractStock(); // Message = $"{cola.ProductName}を購入しました"; // Deposit = balance.GetBalance(); } else { Message = @"金額が足りません"; } } catch (SystemException ex) { System.Console.WriteLine(ex.Message); } } 現時点での検討ポイントは2つ。 検討ポイント 1.飲料を購入したときの判定処理が妥当かどうか判断する。 2.8個のメソッドを1個にまとめる。または、新たにクラスを作成する。 検討ポイント1:飲料を購入したときの判定処理が妥当かどうか判断する まずは、在庫の確認。 ProductクラスのHasStockメソッド public bool HasStock() { // 在庫あり if (Stock > 0) return true; // 在庫切れ return false; } 呼び出し元(MainWindowViewModel)のExecuteBuyColaクラスでは、ColaクラスのHasStockメソッドの戻り値を判定している。 falseの場合は、メッセージを表示する。 次に、BindableBaseクラスのDebitメソッドを呼び出し、残額が、飲料を購入できる金額を上回っているか確認する。 下回っている場合は、falseを返す。 上回っている場合は、Balanceプロパティに投入金額を加算し、trueを返す。   呼び出し元(MainWindowViewModel)のExecuteBuyColaメソッドでは、戻り値を判定している。 trueの場合は、、ColaクラスのSubtractStockメソッドを呼び出し、在庫から1引く。さらに、メッセージを表示、BindableBaseクラスのGetBallanceメソッドでBalanceプロパティの値を取得し、残額を表示する。 falseの場合は、メッセージを表示する。 BindableBaseクラスのDebitメソッド public bool Debit(int amount) { // 残金が不足 if (Balance < amount) return false; // Balance -= amount; return true; } 改善案1 新たにクラスを作成し、メッセージを返すメソッドを作成する。 呼び出し元では、判定処理は行わず、戻り値のメッセージを表示する。 デメリットとしては、エラーの場合にも残額を必ず表示する。 検討ポイント2:8個のメソッドを1個にまとめる 前述の改善案1のクラスのメソッドを呼び出す。 Genericを使ったPrivateメソッドを作成し、呼び出す。 まとめ 現時点で、いい案は浮かばない。 検討(お札・小銭投入ボタン) まずは、お札・小銭投入ボタンをクリックしたときの動作を確認する。 動作 1.お金を投入できるかどうか確認する。  1.自動販売機にお金を投入したときに、残額が上限を下回っているか確認する。 2.お金を投入できた場合  1.残額に投入金額を加算する。  2.残額を表示する。 3.メッセージを表示する。  1.円を投入しました(2のとき)  2.上限金額より大きい金額を投入できません(2の条件を満たさないとき) ソースコードは以下のとおり。ボタンは、6個あるので、これと同様のメソッドが6個存在する。 MainWindowViewModel.csからの抜粋 private void ExecuteInsertCoin10() { try { // if (balance.Credit(10)) { Message = @"10円を投入しました"; // Deposit = balance.GetBalance(); } else { Message = @"上限金額より大きい金額を投入できません"; } } catch (SystemException ex) { System.Console.WriteLine(ex.Message); } } 現時点での検討ポイントは2つ。 検討ポイント 1.お金を投入したときの判定処理が妥当かどうか判断する 2.6個のメソッドを1個にまとめる 検討ポイント1:お金を投入したときの判定処理が妥当かどうか判断する BindableBaseクラスのCreditメソッドは、投入された金額を残額に加算した場合に上限金額を超えるかどうか判定している。 超えた場合は、falseを返す。 超えない場合は、Balanceプロパティに投入金額を加算し、trueを返す。   呼び出し元(MainWindowViewModel)のExecuteInsertCoin10メソッドでは、戻り値を判定している。 trueの場合は、メッセージを表示し、さらに、BindableBaseクラスのGetBallanceメソッドでBalanceプロパティの値を取得し、残額を表示する。 falseの場合は、メッセージを表示する。 BindableBaseクラスのCreditメソッド public bool Credit(int amount) { // 自動販売機の投入金額は、10,000円まで if ((Balance + amount) > MaximaumDepositAmount) return false; // Balance += amount; return true; } なんだか似たような処理が両方のメソッドに実装されている。判定処理は2つ必要か? ここで、一度、実際の自動販売機の動作に立ち返ってみる。仮に、自動販売機に投入するお金の上限が決められていたとしたら、どういう動作(ふるまい)をするだろうか。投入したお金が自動的に戻ってくる(排出される)はずだ。ちなみに、釣銭切れの場合は、お札や小銭が戻ってきてしまう。 では、この機能(上限を判定しメッセージを表示する機能)は、どこに持たせるべきか。 改善案1 Creditメソッドでメッセージを作成し、呼び出し元に返す。 呼び出し元では、判定処理は行わず、戻り値のメッセージを表示する。 デメリットとしては、エラーの場合にも残額を必ず表示する。 改善案2 Creditメソッドの判定処理を分離し、bool型のIsExceedLimitメソッドを作成する。 呼び出し元に、IsExceedLimitメソッドを呼び出す処理を追加する。 IsExceedLimitメソッドの戻り値がtrueの場合、呼び出し元はメッセージを表示する。 IsExceedLimitメソッドの戻り値がfalseの場合、呼び出し元は、 Creditメソッドを呼び出し、戻り値のメッセージを表示する。 改善案3 Creditメソッドの判定処理を分離し、IsExceedLimitメソッドを作成する。 呼び出し元にCanExecuteInsertCoin10メソッドを実装し、判定処理を分離する。 デメリットとしては、残額を超える金額のボタンはマスクされ、クリックできなくなる? もう一つのデメリットとしては、上限を超えた旨のメッセージも表示できない。そうなると、元々の仕様をみたさなくなってしまう。 検討ポイント2:6個のメソッドを1個にまとめる これについては、コマンドパラメータとして金額を渡せば、済むかもしれない。現在、流通している紙幣や小銭の単位が変わることは、考えづらく、パラメータとして固定値を渡すことは、メンテナンス性などに与える影響は少ないと考えられる。電子マネーになってしまえば、話は変わるかもしれない。 ただし、前述の改善案2を採用した場合、CanExecuteInsertCoin10メソッドが期待どおりに動作するかどうかは、不明である。つまり、実装してみないとわからない。 まとめ:操作する人間(ユーザー)は、何を期待するか お札・小銭投入ボタンをクリックしたユーザーが期待するのは、設計されたUIどおり画面に表示されるメッセージ(文章)と残額であるとすると、ボタンをクリックしたときに、取得できるのはメッセージと残額となるように(内部)設計するのがいいのだろうか。それとも、見た目がそうであれば、実装する機能や動作(ふるまい)は、異なってもよいのか。悩ましいところである。 検討(お釣りボタン) お釣りボタンをクリックしたときの動作の概要は以下のとおり。 1.残額を0にする。 2.残額を0を表示する。 3.メッセージを表示する。  残額を排出しました ソースコードは以下のとおり。ボタンは、1個なので、冗長なメソッドは存在しない。今のところ、改善する必要はないように思える。改善するとすれば、表示する金額に桁区切りのカンマを付与することぐらいか。 MainWindowViewModel.csからの抜粋 private void ExecuteChange() { try { // 残額を取得 var change = balance.GetBalance(); Message = $"{change}円を排出しました"; // 残額をクリア(0円) balance.Debit(change); // Deposit = balance.GetBalance(); } catch (SystemException ex) { System.Console.WriteLine(ex.Message); } } 最後に MSDNの技術情報では、まかないきれないことが多々あり、ついついインターネットで情報を検索し、開発者各人の情報をに参考にすることになるのだが、はたして、オリジナリティというか、著作権の侵害については、どうなってしまうのか、悩ましい。学術論文等では、盗作ということになることも巷にはある。では、一般に販売されている書籍や論文などのように、参考書籍、あるいは、参考サイトとしてコード内に記載しておけば、良いのか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniTaskで簡単なProcessをサクッと実行して結果を文字列で得る

概要 macOS BigSur 11.4 Unity 2021.1.28f1 UniTask 2.2.4 Unityでもコマンド実行した結果を使いたいな〜ってときに使えるやつです 使用クラス ProcessStartInfo Process 具体例: Gitのハッシュ値を取得してTextに表示する 特に解説することはなかったので早速コードをドン CommitHashLabel.cs public class CommitHashLabel : MonoBehaviour { [SerializeField] private Text text; private void Start() => InitCommitHashAsync().Forget(); private async UniTaskVoid InitCommitHashAsync() { try { var hash = await GetCommitHashAsync(); text.text = hash; } catch (Exception e) { Console.WriteLine(e); } } private async UniTask<string> GetCommitHashAsync() { var processStartInfo = new ProcessStartInfo { FileName = "git", Arguments = "rev-parse --short HEAD", CreateNoWindow = false, StandardOutputEncoding = Encoding.UTF8, RedirectStandardOutput = true, UseShellExecute = false }; var process = Process.Start(processStartInfo); if (process == null) { throw new ArgumentException("Process cannot start."); } await UniTask.SwitchToThreadPool(); process.WaitForExit(); await UniTask.SwitchToMainThread(); return (await process.StandardOutput.ReadToEndAsync().AsUniTask()).Trim(); } } これで実行時に勝手にコミットハッシュを表示してくれます Startに書いていますがAwakeでやっても大丈夫だと思います Editor上でしか動作しないので使用の際には注意! 補足 ProcessStartInfo ProcessStartInfo.WorkingDirectory 実行場所の指定 指定がないとEnvironment.CurrentDirectoryが指定されます Unityのプロジェクトをgitで管理しているのであれば指定がなくても問題なく使用できると思います Process Process.WaitForExit これをメインスレッドで実行してしまうと処理が止まってしまうのでスレッドプールに移動してから呼ぶことで非同期っぽくしています Unityで.NET6が使えるようになったら WaitForExitAsyncとかいうゴキゲンなメソッドも使えるようになるみたいです 汎用的に使えるようにする ProcessStartInfoの作成とProcessの実行を行うクラスを作成します UniTaskProcessExecutor.cs public class UniTaskProcessExecutor { private readonly ProcessStartInfo processStartInfo; public UniTaskProcessExecutor(string filename, string arguments) { processStartInfo = new ProcessStartInfo { FileName = filename, Arguments = arguments, CreateNoWindow = false, StandardOutputEncoding = Encoding.UTF8, RedirectStandardOutput = true, UseShellExecute = false }; } public async UniTask<string> ExecuteAsync() { var process = Process.Start(processStartInfo); if (process == null) { throw new ArgumentException("Process cannot start."); } await UniTask.SwitchToThreadPool(); process.WaitForExit(); await UniTask.SwitchToMainThread(); return (await process.StandardOutput.ReadToEndAsync().AsUniTask()).Trim(); } } CommitHashLabel.cs public class CommitHashLabel : MonoBehaviour { [SerializeField] private Text text; private void Start() => InitCommitHashAsync().Forget(); private async UniTaskVoid InitCommitHashAsync() { try { var process = new UniTaskProcessExecutor("git", "rev-parse --short HEAD"); var hash = await process.ExecuteAsync(); text.text = hash; } catch (Exception e) { Console.WriteLine(e); } } } クラス名にUniTaskをつけてしまうと検索性が著しく落ちるので別の名前をつけたほうがいいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#を使用してWordの脚注と文末脚注の内容を読み取る方法

C#を使用してWordの脚注と文末脚注の内容を読み取る方法 背景 脚注と文末脚注は、Word文書の本文を補足説明のために追加した内容です。脚注は通常、ページの下部にあり、ドキュメントのコンテンツに注釈を付けるために使用できます。文末脚注は通常、ドキュメントの最後にあり、引用されたドキュメントを説明するために使用できます。この記事では、C#を使用してWord文書の脚注と文末脚注の内容を読み取る方法を紹介します。 Word文書のスクリーンショット: 使用するコンポーネント: Free Spire.Doc 次のコードを使用する前に、C#アプリケーションを作成し、Spire.Doc.dllをプロジェクトに参照する必要があります。 コード一覧 脚注を読み取る(Footnote) //documentのインスタンスを作成する Document doc = new Document(); //Word文書をロードする doc.LoadFromFile("sample.docx"); //Word文書内全ての脚注を取得する List<Footnote> footNotes = doc.Footnotes; //StringBuilderのインスタンスを作成する StringBuilder sb = new StringBuilder(); //脚注の内容をStringBuilderインスタンスに追加する foreach (Footnote footNote in footNotes) { foreach (DocumentObject obj in footNote.TextBody.ChildObjects) { if (obj is Paragraph) { sb.AppendLine((obj as Paragraph).Text); } } } //txtファイルに書き込む File.WriteAllText("脚注.txt", sb.ToString()); 読み取り効果は以下の通りです: 文末脚注を読み取る(EndNotes) //documentのインスタンスを作成する Document doc = new Document(); //Word文書をロードする doc.LoadFromFile("sample.docx"); //Word文書内全ての文末脚注を取得する List<Footnote> endNotes = doc.Endnotes; //StringBuilderのインスタンスを作成する StringBuilder sb = new StringBuilder(); //文末脚注の内容をStringBuilderインスタンスに追加する foreach (Footnote endNote in endNotes) { foreach (DocumentObject obj in endNote.TextBody.ChildObjects) { if (obj is Paragraph) { sb.AppendLine((obj as Paragraph).Text); } } } //txtファイルに書き込む File.WriteAllText("文末脚注.txt", sb.ToString()); 読み取り効果は以下の通りです: 以上は脚注について簡単な操作でした、最後まで読んでいただきありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でカノジョをつくる(1日目)

はじめまして。たなしょです。 ここ最近はPHPでWebサイトの開発保守を担当しています。 クリスマスまでにカノジョを作りたいと思います。 開発備忘録ですので所々文法の間違えがあると思います。ごめんなさい。 なぜやるのか クリスマスも近くなってきて「今年こそはカノジョと一緒にクリスマスを過ごしたい!」ということで、昨今の世間の事情や外になるべく出たくないという自分の性格も考慮して、エンジニアなので「プログラミングでカノジョを作ればいいのか!」と思い付きこの開発を始めることにしました。 なぜC#なのか 仕事でもプライベートでもWindowsPCを使用している関係で、公私両方で彼女と会えるようにWindowsテスクトップアプリを作ればいいのではないのかと思い、「それなら一番簡単に作れるのはC#かなー」と思い付きでC#にしました。 方針など この開発は3つのフェーズに分かれるのかなと考えています。 フェーズ1 今までC#を触ったことがないのでまずは文法の確認に約1週間(12/8目途ぐらいで完了)ほど使おうかなと考えています。このフェーズでは以前C#をやろうとして1年以上本棚に眠っていたスラスラわかるC# 第2版を写経しながらC#の文法を身に着けたいと思います。 フェーズ2 フェーズ1で使用したスラスラわかるC# 第2版を読み終わったら、VisualC#を使ってデスクトップアプリを作っていくので前に本屋さんで立ち読みした際に、僕の作りたいものと同じ発想のものが書いてあった気がしたVisual C# 2022パーフェクトマスターを読みながら作っていこうと思います。 かなり分厚い本なのでところどころ読み飛ばしながら進めていくかと思います。 このフェーズは約2週間(12/22目途ぐらいで完了)ほど使う予定です。 フェーズ3 ここまで本に書いてあるソースを丸々コピーしているだけなので、ここから自分が考えた独自の機能を追加していくフェーズになります。 実際追加できるかどうかはわかりませんがコードの成形などできる限り何かしらやっていこうと思います。 このフェーズは残りの2、3日使って実施していく予定です。 最後に だいたい平日1~2時間、休日2~3時間ほどは捻出できると思うので、25日間開発とブログを頑張りたいです。 ひっそりとやっていければなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SystemInfo.operatingSystemからバージョンを取得するときの注意(iPadOS15.1以降のiPad端末の挙動について)

PONOS Advent Calendar 2021の2日目の記事です。 昨日は@nissy_gpさんの「UnityのGC AllocateとWaitForSeconds」でした。 はじめに UnityのSystemInfo.operatingSystemをご存知でしょうか。 アプリの実行環境のOS情報を取得するプロパティであり、OS名とバージョンを「iOS 15.1」のようなフォーマットの文字列で返してくれます。 SystemInfo-operatingSystem - Unity スクリプトリファレンス 今回は、iPadOS 15.1以降のiPad端末でSystemInfo.operatingSystemを使用していて発生した問題について共有させていただきます。 なお、筆者の手元で今回確認した端末のOSバージョンがiPadOS 15.1でしたので、そちらのバージョンについてのみ記述しています。 iPadOS 15.0 ~ 15.0.2での動作については未確認ですが、同様の挙動をする可能性があります。 また、確認時のビルド環境はUnity 2019.4.32f1およびXcode 13.0です。 発生した問題 前提 SystemInfo.operatingSystemをiOS環境で呼ぶと、「iOS 15.1」のように「iOS」から始まる文字列を返してくれます。 これは、端末の種類がiPhoneであってもiPadであっても同様の挙動でした。 (ちなみに、iOS 10未満の場合は「iPhone OS」から始まる文字列を返していましたが、今回は特に触れません) 私の携わっていたプロジェクトでは、このSystemInfo.operatingSystemから取得できる文字列を利用して、 以下のようなコードでiOSのバージョンを取得して一部機能の利用可否を判定していました。 /// <summary> /// 端末のiOSのバージョンを取得。 /// </summary> /// <returns>iOSのバージョン。</returns> static Version GetiOSVersion() { var operatingSystem = SystemInfo.operatingSystem; // 「iOS」から始まる文字列が返ってくる前提で、「iOS」部分を削除してバージョンの部分のみを残す。 // "iOS 15.1" -> "15.1" var versionString = operatingSystem.Replace("iOS ", ""); // バージョンの文字列をSystem.Versionオブジェクトにパースする。 Version version; if (Version.TryParse(versionString, out version)) { return version; } return null; } #if UNITY_IOS var version = GetiOSVersion(); if(version == null) { return false; } return 13 <= version.majarVersion; // iOS 13以降であれば機能が利用できる。 #endif iPadOS 15.1で問題が発生 アプリのiOS 15対応を進めていたある日、 「iPadOS 15.1のiPad端末で検証しているが、該当機能が有効化されていない」という不具合報告が飛び込んできました。 「該当機能については手を入れていないはずなのに不思議だな…」と思いつつ調査を進めていくと、衝撃の事実が。 なんと、「iPadOS 15.1を搭載したiPad端末」においてSystemInfo.operatingSystemが 「iPadOS 15.1」のような「iPadOS」から始まる文字列を返していることが判明したのです…! 先ほど上で紹介したGetiOSVersion()メソッドはSystemInfo.operatingSystemの前半部分が「iOS」でないと、 バージョンの文字列を抽出することができないので、その後のSystem.Version.TryParse()に失敗します。 GetiOSVersion()メソッドがバージョン情報を返せなかったために、利用可能なバージョンかどうかの判定を行うことができず、 該当機能が「利用不可」として処理されてしまっていた、というわけです…。 iPadOS 15.1上のSystemInfo.operatingSystemの挙動に対応するため、GetiOSVersion()メソッドを修正することになりました。 iPadOSから始まる文字列に対応するための修正 修正方法としてすぐに思いついたのは、「iOS」と「iPadOS」の両方をReplace()で削除する方法でした。 // 「iOS」部分を削除してバージョン文字列の部分のみを残す。 var versionString = operatingSystem.Replace("iOS ", ""); // 「iPadOS」部分を削除してバージョン文字列の部分のみを残す。 versionString = operatingSystem.Replace("iPadOS ", ""); しかし、この修正の場合、今後のバージョンでまた前半の文字列が変化した場合に再度コードを修正する必要が生じてしまいます。 これ以上、前半の文字列の内容に振り回されたくはないため、正規表現でバージョン情報を取得する方法で修正することにしました。 /// <summary> /// 端末のiOSのバージョンを取得。 /// </summary> /// <returns>iOSのバージョン。</returns> static Version GetiOSVersion() { var operatingSystem = SystemInfo.operatingSystem; // 「数字」から始まり、「.(ピリオド)」と「数字」が連続する文字列を探し出す。 var regex = new System.Text.RegularExpressions.Regex("([0-9]+)(\\.[0-9]+)*"); var match = regex.Match(operatingSystemString); if (!match.Success) { return null; } Version version; if (Version.TryParse(match.Value, out version)) { return version; } return null; } これなら、前半の文字列が「iOS」でも「iPhoneOS」でも「iPadOS」でもバージョン情報を抽出することができます! まとめ SystemInfo.operatingSystemを利用してOSバージョンを判定している場合、 判定方法によっては新しいOSバージョンがリリースされたときに動作不備が発生することがありますので注意しましょう。 明日の担当も私@e73ryoです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?メガネをかけたFANZA女優をピックアップした

この記事は 驚異のFANZA女優検索 Advent Calendar 2021 の 1 日目の記事です。 メガネをかけたFANZA女優一覧 FANZA女優からメガネをかけた女優だけをピックアップしたページを作った。メガネをかけたFANZA女優一覧だ。あくまでDMMが提供している女優の画像がメガネをかけているかが分かるだけだが、公式にその女優を表す画像として提供されている顔写真がメガネをかけているかは割と重要な指針になると思う(何の?!)。実際に本家FANZAにもめがねというジャンルがあることはあるがそういうことじゃない感が強い。その点、自分のページはちゃんとほぼ(認識の精度のせいで残念ながら100%ではない。2人メガネをかけていない女優がいた)メガネをかけている。こういうのがいい。 Azure Face API 作ったきっかけは、ちょっと思う所があって顔認識を調べていてAzure Face APIをみたことだ。HPを見ると「1 人以上の人間の顔と、ポーズ、フェイス マスク、顔ひげなどの各種の属性を検出できます。」と書いてある。属性に何があるか調べて顔属性を見てみた。すると以下のようなパラメータが取れると書いてあった。…!。メガネを認識できるじゃないか!これは是非FANZA女優からメガネをかけた女優一覧を作らねばと思った。 - アクセサリ - 年齢 - ぼかし - 感情 - 露出 - 顔ひげ - 性別 - 眼鏡 - 髪の毛 - 頭部姿勢 - 化粧 - マスク - ノイズ - オクルージョン - 笑顔 Cognitive Services の docsがとても便利 AzureのCognitive ServicesのFace APIを作って、エンドポイントとキーを取得しておけば、docsが非常に良く出来てドキュメントとして読めるだけでなく、APIを直接POSTすることが出来る。今回は眼鏡だけで良いので、returnFaceAttributesをglassesにして後は全部消した。 これでSendボタンを押せばglassesのレスポンスが返ってくる。 faceAttributesがNoGlassesならメガネなし、ReadingGlassesならメガネだ! C#のコードで書く docsのページで試して上手くいったら、これをC#に持ってきてコードにする。これで画像を投げればその画像がメガネをかけているかは判断出来るようになった。全女優の画像を全てこのメソッドにかけて、その結果をデータベースに反映した。 internal async Task<bool> HasGlassesAsync(string actressImageUrl) { //女優の画像URLからHttpContentを取得 var content = await GetImageContent(actressImageUrl); //Headers.ContentTypeにapplication/octet-streamを設定 content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); //Face APIのdetectでreturnFaceAttributesだけを指定してPOST var responseMessage = await HttpClient.PostAsync($"{AzureFaceApiUrl}/detect?returnFaceAttributes=glasses", content); //レスポンスからJSONを取得 var json = await responseMessage.Content.ReadAsStringAsync(); //JSONをデシアライズ var detectResponse = JsonSerializer.Deserialize<DetectResponse[]>(json); //faceAttributes.glassesがReadingGlassesならメガネ。それ以外ならメガネなし。 return detectResponse[0]?.faceAttributes.glasses == "ReadingGlasses"; } DBで絞り込む。 後はデータベースをその条件=メガネで絞り込んだ女優を表示すればOKだ。 SELECT * FROM Actress Where HasGlasses = 1 まとめ これで出来上がったのがメガネをかけたFANZA女優一覧だ。メガネ好きの人がいれば是非見て欲しい。そうでない人も見てみると今まで見えなかったものが見えるかもしれない。メガネだけに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET 6でLINQに追加されたメソッド

.NET 6において、LINQにいくつかのメソッドが追加されました。また、既存のLINQメソッドに新たなオーバーロードが追加されました。非常に便利なメソッド、「一見するとこれは便利なのか?」と疑問が浮かぶけれど実は活躍するメソッドなど、様々なメソッドがあります。 この投稿では、そんな.NET 6において追加されたLINQメソッドを紹介します。 指定した条件での最大・最小を探す「MinBy、MaxBy」 このようなPlayer型と record Player(int level, string name); Player型の配列、playersがあります。 var players = new [] { new Player(level : 10, name : "taro"), new Player(level : 8, name : "jiro"), new Player(level : 5, name : "saburo"), new Player(level : 10, name : "shiro"), }; playersの中から、一番levelが高いPlayer要素を求めるにはどうすればいいでしょうか?もともとLINQにはMaxというメソッドがあります。残念ながら、Maxでは「playersの中の最大レベル」を求める事はできても、「最大レベルを持つ要素」を求めることができません。 // maxLevelは10となる // 「playersの中の最大レベル」は求められた // けれど、「最大レベルを持つ要素」は求められない var maxLevel = players.Max(p => p.level); このような場合、.NET 6において追加されたMaxByメソッドが活躍します。MaxByメソッドは、IEnumerable<T>の要素の中から指定した条件での最大要素を求めることができるメソッドです。このMaxByメソッドを使うことで、次のようにplayers中から最大レベルを持つ要素を簡単に見つけることができます。 // maxLevelPlayerはPlayer型で、 // Player { level = 10, name = taro } var maxLevelPlayer = players.MaxBy(p => p.level); このように、IEnumerable<T>の要素の中から指定した条件での最大要素を求めたい場合はMaxByが、最小要素を求めたい場合はMinByが活躍します。 IEnumerable<T>のMaxByとMinByの返値型が、T型で、IReadOnlyList<T>やT[]型でないことに注目してください。「指定した条件での最大」を満たす要素が複数存在した場合、複数個の要素は返しません。最大となる最初の要素を返します。 // level 10が二人いる。taroとshiro var players = new [] { new Player(level : 10, name : "taro"), new Player(level : 8, name : "jiro"), new Player(level : 5, name : "saburo"), new Player(level : 10, name : "shiro"), }; // maxLevelPlayerはPlayer { level = 10, name = taro } // Player { level = 10, name = shiro }ではないことに注意 var maxLevelPlayer = players.MaxBy(p => p.level); また、IEnumerable<T>の要素が空だった場合のMaxBy・MinByの挙動にも注意してください。 T型が参照型:nullが帰ってくる T型が値型:InvalidOperationExceptionが投げられる IEnumerable<T>が参照型で、全ての要素がnullな場合もnullが帰ってきます。 公式ドキュメント(MaxBy) 公式ドキュメント(MinBy) ソースコード(MaxBy) ソースコード(MinBy) 実装PR 提案Issue 3つのIEnumerable<T>もまとめて「Zip」 2つのIEnumerable<T>を同じインデックスごとにまとめて、1つのIEnumerable<T>にしたい場合があります。こんな時に活躍するのがZipメソッドです。 Zipメソッド(のオーバーロードの一つ)は、次の例のように2つのIEnumerable<T>を同じインデックスの要素ごとValueTuple型としてまとめてくれます。 var descriptions = new string[]{ "すごい", "まぁまぁ", "がんばれ" }; var values = new []{ 200, 100, 20, 5, 2, }; // 要素はValueTuple<int, string>型 // 要素長は一番短いdescriptionsの長さに合わせて3となる IEnumerable<ValueTuple<int, string>> zipped = values.Zip(descriptions); // 次のように表示される // (200, すごい) // (100, まぁまぁ) // (20, がんばれ) foreach(ValueTuple<int, string> valueTuple in zipped) { Console.WriteLine(valueTuple); } // resultSelectorを渡すオーバーロードもある // resultSelectorは、IEnumerable<T>`の要素を、どのように合成するか指定するデリゲート // IEnumerable<string> valuesAndKeys3 = values.Zip(descriptions, (v, d) => $"value: {v}, description:{d}"); 今までZipメソッドには、2つのIEnumerable<T>をまとめるオーバーロードしかありませんでした。.NET 6から、3個のIEnumerable<T>をまとめることができるオーバーロードが加わりました。 var descriptions = new string[]{ "すごい", "まぁまぁ", "がんばれ" }; var values = new []{ 200, 100, 20, 5, 2, }; var keys = new []{ "RankA", "RankB", "RankC", "RankD", }; // 要素はValueTuple<int, string, string>型 // 要素長は一番短いdescriptionsの長さに合わせて3となる IEnumerable<ValueTuple<int, string, string>> zipped = values.Zip(descriptions, descriptions); // 次のように表示される // (200, すごい, RankA) // (100, まぁまぁ, RankB) // (20, がんばれ, RankC) foreach(ValueTuple<int, string, string> valueTuple in zipped) { Console.WriteLine(valueTuple); } なお、こちらのGitHub issueのコメントによると 3個までIEnumerable<T>をまとめるオーバーロードまでをサポート、4個以上のIEnumerable<T>をまとめるはオーバーロードはサポートしない resultSelectorを渡すオーバーロードに、3個のIEnumerable<T>まとめるオーバーロードを追加しない とのことです。 公式ドキュメント(Zip) ソースコード(Zip) 実装PR 提案Issue デフォルトを指定可能な「FirstOrDefault、LastOrDefault、SingleOrDefault」 このようなPosition型があります。 record Position(int X, int Y) { } 次のコードは、Position型とFirstOrDefultメソッドの利用例です。 // 現在のPositionを読み込んで・・・ Position currentPosition = LoadCurrentPosition(); // Positionの配列を読み込んで・・・ Position[] positions = LoadPositions(); // デフォルトのポジションを読み込んで・・・ Position defaultTargetPosition = LoadDefualtPosition() // positionsの中から、一番最初の近い(IsNearがtrueを返す)要素を返す // もしpositionsの中に近い(IsNearがtrueを返す)要素がなかったら、デフォルトのポジションとする Position targetPosition = positions     .FirstOrDefault(p => IsNear(p, currentPosition)) ?? defaultTargetPosition; FirstOrDefaultメソッドは、条件を満たす要素が存在しなかった場合、型の規定値を返します。Positionは参照型なので、上のコードではFirstOrDefaultメソッドはnullを返します。上のコードでは??演算子を使い、nullであればLoadDefualtPositionの結果を、targetPositionに代入します。 .NET 6から FirstOrDefault LastOrDefault SingleOrDefault メソッドに、要素が空だった・満たす要素がなかった場合の規定値を指定するオーバーロードが、それぞれ加わりました。このオーバーロードを使って、先のコードを書き換えると次のようになります。 // 現在のPositionを読み込んで・・・ Position currentPosition = LoadCurrentPosition(); // Positionの配列を読み込んで・・・ Position[] positions = LoadPositions(); // デフォルトのポジションを読み込んで・・・ Position defaultTargetPosition = LoadDefualtPosition() // positionsの中から、一番最初の近い(IsNearがtrueを返す)要素を返す // もしpositionsの中に近い(IsNearがtrueを返す)要素がなかったら、引数に指定したdefaultTargetPositionを返す // .NET 6から加わったオーバーロード Position targetPosition = positions     .FirstOrDefault(p => IsNear(p, currentPosition), defaultTargetPosition); 公式ドキュメント(FirstOrDefualt) 公式ドキュメント(LastOrDefualt) 公式ドキュメント(SingleOrDefualt) ソースコード(FirstOrDefault) ソースコード(LastOrDefault) ソースコード(SingleOrDefault) 実装PR 提案Issue 集合演算も指定した条件で「DistinctBy, ExceptBy, IntersectBy, UnionBy」 LINQにはIEnumerable<T>の重複要素を排除するDistinctというメソッドがあります。 IEnumerable<string> ids = new [] { "aaa", "bbb", "aaa", "ccc", "ddd", "aaa", "ccc", "aaa", "aaa" }; // 次のように表示される // aaa // ccc // bbb // ddd foreach(var id in ids.Distinct()) { Console.WriteLine(id); } .NET 6から、このDistinctに似たDistinctByというメソッドが追加されました。DistinctByを使うことで、重複比較する方法を指定できます。次のようなStatus型とLog型を使って、DistinctByの例を示します。 enum Status { Pending, Failure, Success, } record Log(String id, Status status) { } 実行結果を見ると重複と判定された要素のうち、先の要素は残り、後の要素が排除されていることに注目してください。 var logs = new [] { new Log("aaa", Status.Pending), new Log("bbb", Status.Pending), new Log("ccc", Status.Pending), new Log("ddd", Status.Pending), new Log("aaa", Status.Success), new Log("aaa", Status.Pending), new Log("aaa", Status.Success), new Log("ccc", Status.Success), new Log("aaa", Status.Failure), }; // Logのidが同じ要素を重複排除 IEnumerable<Log> distinctBy = logs.DistinctBy<Log, string>(log => log.id); // 次のように表示される // Log { id = aaa, status = Pending } // Log { id = bbb, status = Pending } // Log { id = ccc, status = Pending } // Log { id = ddd, status = Pending } foreach(var log in distinctBy) { Console.WriteLine(log); } またLINQにはIEnumerable<T>の差集合をもとめる「Except」、積集合をもとめる「Intersect」、和集合をもとめる「Union」というメソッドがあります。.NET 6でこれらとよく似た「ExceptBy」、「IntersectBy」、「UnionBy」というメソッドが追加されました。それぞれの利用例を示します。 var logs = new [] { new Log("aaa", Status.Pending), new Log("bbb", Status.Pending), new Log("ccc", Status.Pending), new Log("ddd", Status.Pending), new Log("aaa", Status.Success), new Log("aaa", Status.Pending), new Log("aaa", Status.Success), new Log("ccc", Status.Success), new Log("aaa", Status.Failure), }; var anotherLogs = new [] { new Log("ddd", Status.Success), new Log("eee", Status.Success), new Log("fff", Status.Failure), }; Console.WriteLine("--ExceptBy--"); // logsの要素から、idが"aaa"と"bbb"を除いた差集合を求める IEnumerable<Log> exceptBy = logs.ExceptBy<Log, string>(new []{"aaa", "bbb"}, a => a.id); // 次のように表示される // Log { id = ccc, status = Pending } // Log { id = ddd, status = Pending } foreach(var log in exceptBy) { Console.WriteLine(log); } Console.WriteLine("--IntersectBy--"); // logsの要素から、idが"aaa"と"bbb"と交差する積集合を求める IEnumerable<Log> intersectBy = logs.IntersectBy<Log, string>(new []{"aaa", "bbb"}, a => a.id); // 次のように表示される // Log { id = aaa, status = Pending } // Log { id = bbb, status = Pending } foreach(var log in intersectBy) { Console.WriteLine(log); } Console.WriteLine("--UnionBy--"); // logsとanotherLogsのid比較による和集合を求める IEnumerable<Log> unionBy = logs.UnionBy<Log, string>(anotherLogs, a => a.id); // 次のように表示される // Log { id = aaa, status = Pending } // Log { id = bbb, status = Pending } // Log { id = ccc, status = Pending } // Log { id = ddd, status = Pending } // Log { id = eee, status = Success } // Log { id = fff, status = Failure } foreach(var log in unionBy) { Console.WriteLine(log); } 公式ドキュメント(DistinctBy) 公式ドキュメント(ExceptBy) 公式ドキュメント(IntersectBy) 公式ドキュメント(UnionBy) ソースコード(DistinctBy) ソースコード(ExceptBy) ソースコード(IntersectBy) ソースコード(UnionBy) 実装PR 提案Issue 指定した数ごとにまとめる 「Chunk」 IEnumerable<T>を指定した要素ごとにまとめる「Chunk」メソッドが加わりました。結構需要が高いやつ! 指定できるのは「まとめる数」だけがです。 Rxやコレクションライブラリなどでよくある「3個ずつまとめて、2個ずつずらしてまとめる」みたいなことは、今のところChunkではできません。 また余った最後の方の要素は、破棄されずまとめる数より少ない数でもまとめられます。 using System; using System.Linq; var strings = new []{ "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", }; // 2個ずつまとめる、次のように表示される // aaa,bbb // ccc,ddd // eee,fff // ggg foreach(var item in strings.Chunk(2)) { Console.WriteLine(string.Join(",", item)); } // 3個ずつまとめる、次のように表示される // aaa,bbb,ccc // ddd,eee,fff // ggg foreach(var item in strings.Chunk(3)) { Console.WriteLine(string.Join(",", item)); } 公式ドキュメント(Chunk) ソースコード(Chunk) 実装PR 提案Issue Index型で指定「ElementAt」 ElementAtにIndex型を引数に取るオーバーロードが加わりました。 using System; using System.Linq; var strings = new []{ "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", }.AsEnumerable(); // 最初の要素を指すIndex型のオブジェクト Index indexFirst = new Index(value: 0, fromEnd: false); // Index型を引数に取るオーバーロードが加わった Console.WriteLine(strings.ElementAt(indexFirst)); // 「aaa」と表示 ↑の使い方では特にメリットは感じませんが、「後ろから何番目」という指定で要素を指定したい場合、Index型を引数に取るオーバーロードが活躍します。 今まではIEnumerable<T>のオブジェクトによっては、最後からn番目の要素をElementAtするには Count(長さを求める)ために全要素列挙を一回 ElementAtでも一回(この列挙は全要素列挙ではない) 計2回の列挙が必要になる場合が次のコードのようにありました。 // 今まで最後からn番目の要素を取得するためにはこうやって書いていた // こうやって書くとCount(要素長を求める)ために一回、ElementAtでも一回(この列挙は全要素列挙ではない)、計2回の要素列挙が必要 // 最後から2番目の要素 int targetIndexInt = strings.Count() - 2 - 1; Console.WriteLine(strings.ElementAt(targetIndexInt)); // 「eee」と表示 ところが、「後ろから何番目」を指定するIndex型のオブジェクトを渡すElementAtのオーバーロードでは、全要素の列挙一回で済みます。 // 末尾の要素を指すIndex型のオブジェクト Index indexLast = new Index(value: 1, fromEnd: true); // 末尾の要素や末尾から何番目という要素を取得するのにIndex型を引数に取るオーバーロードが便利 // Indexを取得するオーバーロードならば、列挙は1回でいい(ただし全要素の列挙) Console.WriteLine(strings.ElementAt(indexLast)); // 「ggg」と表示 Console.WriteLine(strings.ElementAt(new Index(value: 3, fromEnd: true))); // 「eee」と表示 // もちろん、Index型を生成する単項^演算子を使っても便利 Console.WriteLine(strings.ElementAt(^1)); // 「ggg」と表示 Index型の活躍の場面が増えましたね。 公式ドキュメント(ElementAt) ソースコード(ElementAt) 実装PR 提案Issue Range型で指定「Take」 Index型だけでなく、Range型の活躍の場も増えました。TakeメソッドにRange型の引数を取るオーバーロードが加わりました。 既存のTakeメソッドは、IEnumerable<T>の最初のn個の要素を列挙するメソッドです。 var strings = new []{ "000", "111", "222", "333", "444", "555", "666", }.AsEnumerable(); // 最初から0番目から2番目の3要素を取得する var targets = strings.Take(3); Console.WriteLine(string.Join(",", targets.ToArray())); // 「000,111,222」と表示 さてこのTakeメソッドを使って、「2番目から4番目の要素を取得する」ことを考えます。Skipメソッドと合わせてこんなふうに書けば実現できます。 // 最初から数えて2番目から4番目を取得する var targets2to4 = strings     .Skip(2) // Skipメソッドで最初の0番目要素と1番目要素、計2個をスキップ     .Take(3); // Takeメソッドで3個の要素を取得する(2番目要素、3番目要素、4番目要素) Console.WriteLine(string.Join(",", targets2to4.ToArray())); // 「222,333,444」と表示 SkipメソッドとTakeメソッドを使えば、「最初から数えて、n番目からm番目の要素を取得する」というコードは、このように記述できます。 このような範囲指定でIEnumerable<T>から要素を取得する処理は、新たに加わったRange型の引数を取るTakeメソッドのオーバーロードを使うことで、次のように記述できます。 // 0番目、1番目、2番目を取得する。3番目は含まないことに注意 var targets0to3 = strings.Take(0..3); Console.WriteLine(string.Join(",", targets0to3.ToArray())); // 「000,111,222」と表示 // 2番目、3番目、4番目、5番目を取得する。6番目は含まないことに注意 var targets2to5 = strings.Take(2..6); Console.WriteLine(string.Join(",", targets2to5.ToArray())); // 「222,333,444,555」と表示 // 最初から最後までを取得する var targets0ToLast = strings.Take(0..^0); Console.WriteLine(string.Join(",", targets0ToLast.ToArray())); // 「000,111,222,333,444,555,666」と表示 // 1番目から最後の1つ前までを取得する var targets1ToLastPre1 = strings.Take(1..^1); Console.WriteLine(string.Join(",", targets1ToLastPre1.ToArray())); // 「111,222,333,444,555」と表示 // 2番目から最後の2つ前までを取得する var targets2ToLastPre2 = strings.Take(2..^2); Console.WriteLine(string.Join(",", targets2ToLastPre2.ToArray())); // 「222,333,444」と表示 // 10番目から最後の10つ前までを取得する(そんな要素はないから列挙できない) var targets6ToLastPre6 = strings.Take(10..^10); Console.WriteLine(string.Join(",", targets6ToLastPre6.ToArray())); // 「」と表示 Rangeの定義として、「0..3」は、3番目を含まないことに注意してください。 公式ドキュメント(Take) ソースコード(Take) 実装PR 提案Issue 列挙しないで要素数を求められる場合だけ要素数を求める「TryGetNonEnumeratedCount」 一見「これいつ使うんだ?」と思う人もいるかもしれないけれど、実は活躍する「TryGetNonEnumeratedCount」。 このメソッドは、列挙せずとも要素数を求めることができるならば、要素数を求めるメソッドです。返り値としてboolを返して、out引数としてint型のcountを取ります。 public static bool TryGetNonEnumeratedCount<TSource> (this IEnumerable<TSource> source, out int count); TryGetNonEnumeratedCountの呼び出し元(this引数source)が、「列挙しないと要素数を数えることができない」場合は、返り値としてfalseを返します。 var targets = Repeat(); // この場合、TryGetNonEnumeratedCountはfalseを返して「count失敗」と表示されます。 if(targets.TryGetNonEnumeratedCount(out var countRepeat)) { Console.WriteLine(countRepeat); } else { Console.WriteLine("count失敗"); } IEnumerable<String> Repeat() { while(true) { yield return "hello"; } } TryGetNonEnumeratedCountの呼び出し元(this引数source)が、「列挙しないでも要素数を数えることができる」場合は、返り値としてtrueを返し、out引数countに要素数を返します。 var targets = new [] { "aaa", "bbb", "ccc", "ddd", }; // この場合、TryGetNonEnumeratedCountはtrueを返して、countに4が代入されます。 // 「4」と表示されます。 if(targets.TryGetNonEnumeratedCount(out var count)) { Console.WriteLine(count); } else { Console.WriteLine("count失敗"); } 配列やリストなどIColectionインターフェースを実装している型のオブジェクトは、列挙しないでも要素数を数えることができます。 またLINQの結果のオブジェクトで、「これは列挙しないと要素数がわからないのでは?」という場合も、internalな「IIListProvider」というインターフェースを実装している場合、列挙せずとも要素数を取得できる場合があります。 // LINQの結果オブジェクト // 列挙しないと、これは列挙しないと要素数がわからなさそう var targets = new [] { "aaa", "bbb", "ccc", "ddd", }.Select(it => it.Length); // でも内部的にSelectの結果はIIListProviderを実装しているので、列挙せずとも長さがが分かる // この場合、TryGetNonEnumeratedCountはtrueを返して、countに4が代入されます。 // 「4」と表示されます。 if(targets.TryGetNonEnumeratedCount(out var count)) { Console.WriteLine(count); } else { Console.WriteLine("count失敗"); } 一見すると「どこで使うんだ」という人もいるかもしれません。実は先に紹介した「Indexを引数に取るElementAtメソッド」や「Rangeを引数に取るTakeメソッド」の内部で利用され、活躍しています。 公式ドキュメント(TryGetNonEnumeratedCount) ソースコード(TryGetNonEnumeratedCount) 実装PR 提案Issue まとめ この投稿では、.NET 6で追加されたLINQメソッド・既存のLINQメソッドに追加された新たなオーバーロードを紹介しました。非常に使いどころの多い便利なメソッド、「一見するとこれは便利なのか?」と疑問が浮かぶけれど実は活躍するメソッドなど、様々なメソッドが追加されました。ぜひあなたの.NET 6プロジェクトでも活用してください。 関連リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む