20210418のC#に関する記事は6件です。

AzureFunctionsとPowerAppsでぬり絵アプリを作ってみた

概要 PowerAppsで自律神経を整えよう、ぬり絵アプリを作ってみました。 PowerAppsにはペン入力がありますが現状背景画像を設定できないので、下絵画像とペン入力画像を合成をAzureFunctions(C#) でAPIを作って対応しました 応用すると、現場報告系のアプリで、撮影した写真に◯を書いて保存するようなアプリも作成可能です。 ※ペンタブで塗っています。 アプリの仕様 塗り絵の下絵はギャラリーから選べる(SharePointリストに) 塗った絵に名前を付けて保存できる(AzureFunctions/Automate) あとで作品一覧を閲覧できる(ドキュメントライブラリ) 以前Remove.bgというサービスを使って手軽にお絵かきアプリを実現しましたが、無料プランだと回数制限があったり、性能の無駄遣い感があったため、自前でC#を使ってAzureFunctions上にAPIを作ってみました。 同じものを作るのも面白くないため、何か考えた結果ぬり絵アプリになりました。 PowerAppsにない機能は外部のAPI/自前のAPIを使って実現できるのが素晴らしいですね。 作成方法 工夫が必要な部分だけ説明してみます。 APIの呼び出しは手軽にPowerAutomateを経由してHTTPでAzureFunctionsを呼び出しました。 HTTPを利用するためコミュニティプランか有償プランが必要です。 for M365プラン内でやりたい場合、Dataverse for Teams環境からAzure API Managementを経由して呼び出します。 覚え書き程度の糞記事ですが→https://qiita.com/Rambosan/items/bea833527d97c8ec6c4e 構成 SharePointリスト:ぬり絵の下地となる画像と画像サイズの情報を格納します。 PowerApps:ぬり絵アプリ本体です。ぬり絵をリストから選ぶ→塗り塗り→Automate経由でAPI呼び出し合成→名前をつけてドキュメントライブラリに保存。 AzureFunction:Automateから受け取った2つの画像を合成します。 PowerAutomate:PowerAppsから背景画像(ぬり絵下地)、前景画像(ペン入力)を取得して画像合成APIを呼び出し、PowerAppsに画像を返します。 ドキュメントライブラリ:作品を保存するライブラリです。 SharePointリスト 下絵を管理するリストです。 タイトルの他、幅、高さが入力できるようにし、添付ファイルに下絵画像を添付します。 画像によって縦横のサイズが違うため、後でサイズ情報が必要になります。 PowerApps ペン入力の画像を下絵とズレないようにするためには、下絵画像とペン入力を同じ縦横比になるよう調整します。 ペン入力から出力される画像サイズはコントロールのサイズとなりますので、これを利用します。 塗り絵選択画面 SharePointリストから下絵を読み込みギャラリーで表示します ギャラリーのOnClickでぬり絵に移動、画面引数で画像、縦、横を渡します。 OnClick Reset(PenInput_NurieFg); Navigate(EditScreen_Nurie,ScreenTransition.Cover,{Width:ThisItem.Width,Height:ThisItem.Height,Image:Image_Thumbnail.Image}) 塗り絵画面 画像コントロールとペン入力コントロールを組み合わせて、塗り絵をしているように見せかけます。 画像の大きさは下絵によってバラバラなので、選んだ画像のサイズによって2つコントロールの大きさを変更してあげます。 ペン入力から出力される画像サイズはコントロールのサイズとなります。これを画像コントロールに合わせて調整してあげることで同じ縦横比で画像を作ることができます。 ①画面のOnVisible。ぬり絵画面の最大サイズを設定してあげて、縦横比を維持したまま画像サイズを決定します。 Width、Heightは塗り絵選択画面から受け取った画面変数で、下絵画像のサイズが入っています。 OnVisible With( //最大幅、高さを設定 {_maxWidth:1000,_maxHeight:600}, With( {_minAspectRatio:Min(_maxWidth/Width,_maxHeight/Height)}, UpdateContext({_ImageWidth:Width * _minAspectRatio}); UpdateContext({_ImageHeight:Height * _minAspectRatio}); ); ) ②画像コントロール Imageプロパティに画面引数で受け取ったImageをセットします。 Width、HeightはOnVisibleで計算した_ImageWidth、_ImageHeightを設定します。 ③ペン入力 ペン入力ツールバーの✕ボタンは、塗り絵中に押してしまうと発狂するため、四角アイコンで隠しておきます。 ※消しゴムアイコンでResetする Widthは_ImageWidth、Heightは_ImageHeight+60を設定。 この60pxはツールバーの領域です。 ペン入力から出力される画像はツールバー部分も含まれますが、後のAPIで下60pxはクロップします。 ④保存ボタン ここで、この後のPowerAutomateを呼び出して画像を合成します。 その後保存画面に移動させます。 Set( MergedNurieImage, PowerApps塗り絵合成フロー.Run( Substitute(JSON(PenInput_NurieFg.Image,IncludeBinaryData),"""",""), Substitute(JSON(Image_NurieBg.Image,IncludeBinaryData),"""","") ) ); 保存画面 APIから受け取った合成後の画像を表示し、名前を入力して保存できる画面です。 後になりますが、MergedNurieImage.response.dataには画像のDataUriが入るので、 画像コントロールのImageに設定します。 後は作品名の入力欄を用意し、保存ボタンクリックで画像をドキュメントライブラリに保存するフローを実行します。 作品画面 ギャラリーコントロールでドキュメントライブラリを表示するだけです。 PowerAutomate ①画像を合成するフロー 比較的単純です 全体 HTTP部分(URLにはAzureFunctionのURIを入れます。(Functionキー付きの ②画像の保存フロー こちらのテンプレートを使って改造します。 PowerAppsからファイル名とファイルコンテンツを受け取ってドキュメントライブラリに保存するだけです。 https://japan.flow.microsoft.com/ja-jp/galleries/public/templates/fe971b57c1994482b565ccfce936900d/upload-a-photo-to-sharepoint-from-power-apps/ AzureFunctions C#で作った動くだけのKUSOコードです。 github バリデーションやらでコード全体は長くなりましたが、メイン関数はこれだけです。 ペン入力の画像は背景が透過なので、画像の合成はGraphicsを使った単純な処理で済みます。 MergeImages.cs public static class MergeImages { [FunctionName("MergePowerAppsImages")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log){ log.LogInformation("C# HTTP trigger function processed a request."); //バリデーション HttpRequestBody<NurieImageEntity> requestBody; try { requestBody = await req.GetBodyAsync<NurieImageEntity>(); } catch (Exception e) { return new BadRequestObjectResult(e.Message); } if (!requestBody.IsValid) { return new BadRequestObjectResult(requestBody.ValidationResults.First().ErrorMessage); } NurieImageEntity entity = requestBody.Value; //ビットマップへの変換 Bitmap fgBitmap, bgBitmap; try { fgBitmap = entity.image_fg_datauri.ReadAsBitmap(); } catch (Exception) { return new BadRequestObjectResult($"{nameof(entity.image_fg_datauri)}の変換に失敗しました。"); } try { bgBitmap = entity.image_bg_datauri.ReadAsBitmap(); } catch (Exception) { return new BadRequestObjectResult($"{nameof(entity.image_bg_datauri)}の変換に失敗しました。"); } //ペン入力ツールバーのクロップ有無 var cropFgToolbar = false; if (entity.image_fg_crop == "true") cropFgToolbar = true; //画像の合成 var nurieBitmap = MergeTwoImages(fgBitmap, bgBitmap, cropFgToolbar); var dataUri = new ImageDataUri(ImageFormat.Png, nurieBitmap); object resultJson = new { data = dataUri.ToString() }; return new JsonResult(resultJson); } /// <summary> /// 背景画像に前景画像を描画してビットマップを返します。 /// </summary> /// <param name="fg">前景画像</param> /// <param name="bg">背景画像</param> /// <param name="cropToolbar">ツールバーの60pxを切り抜くかどうか</param> /// <returns></returns> public static Bitmap MergeTwoImages(Bitmap fg, Bitmap bg, bool cropToolbar = false) { var bitmapBase = new Bitmap(bg); //ツールバーの60pxを切り抜き Bitmap fgBitmap; if (cropToolbar) { fgBitmap = fg.Clone(new Rectangle(0, 0, fg.Width, fg.Height - 60), fg.PixelFormat); } else { fgBitmap = fg; } //ビットマップに前景画像を描画 using (var g = Graphics.FromImage(bitmapBase)) { g.DrawImage(fgBitmap, g.VisibleClipBounds); } return bitmapBase; } 参考資料など HttpRequestのバリデーション https://tsuyoshiushio.medium.com/how-to-validate-request-for-azure-functions-e6488c028a41 塗り絵素材はぬりえパークさんからいただきました。 https://nurie-park.com/ AzureFunctions https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio あとがき PowerAppsで不足する機能をAzureFunctionsを使って強化しました。 撮影した画像に描画するといった要件は現場系だとあると思うので、ペン入力の背景画像を設定できるようになってほしいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#とJetBrains RiderでUbuntuのGUIを開発する

Ubuntu上でC# GUIを開発する時いろいろなIDEやフレームワークがあり、それぞれに長所と短所がある 開発環境 Ubuntu 18 要件 理由 .net Core Sdkで開発したい 開発者が多い monoより.net Coreのほうがで検索でヒットする glade gtk3を利用したい .net Coreに対応しているから VisualSutdio風のIDEを使いたい Windowsで使い慣れているから。 IDEとフレームワークの種類 IDE SDK エディター デザイン デメリット 費用 MonoDevelop Mono VisaulStudioに慣れていると使いづらい GTK2デザイナー .net coreが利用できないっぽい? IDEの見栄えが悪い 無料 VisalStudio Code .net Core VisaulStudio風にカスタマイズすれば近づけるが手順が多く煩雑 抜けが発生する Gtk3 glade いろいろ拡張機能をインストール必要がある。  無料 Rider .net Core Mono Visual Studio風で使いやすい 便利な機能が既に組み込まれている Gtk3 glade 毎月料金が発生する 月1600円 開発環境がすぐ構築できそうななので Riderを利用することにした。 とりあえず試用期間30日を利用してみる JetBrains Riderをインストールする $ sudo snap install rider --classic Download Riderの日本語化 Settings/Preferences→Plugins→Marketplace より “japanese” を検索して見つかる”Japanese Language Pack”をインストールする Riderを起動する Ubunchの環境でConsoleアプリの開発環境がすぐ出てくる 標準で.net Core がインストールされている。 三角のボタンをクリックするとビルドができます。 Nugetはどこにあるのか? 左下にありました。右クリックからソフトをインストールできます。 エラーが出たときに最適なライブラリをNugetから検索してくれる機能 ライブラリがインポートされていなくてエラーが出た時に、右クリックから最適なライブラリをNugetから検索してにいってくれます GladeとGTK3を組み合わせてGUIアプリを作る GTK+-3.0をインストール。 $ sudo apt install libgtk-3-dev GTK3デザイナ gladeをインストール。 $ sudo apt install glade GladeはGUIをデザインするソフトです。日本語にも対応しています。このGladeファイルと Riderと.net Coreを併用してGUIアプリを開発していきます。 続く GTK+3入門 GTKではじめるGUIアプリケーション開発
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#とJetBrains RiderでUbuntuのGUIアプリを開発する

Ubuntu上でC# GUIを開発する時いろいろなIDEやフレームワークがあり、それぞれに長所と短所がある 開発環境 Ubuntu 18 要件 理由 .net Core Sdkで開発したい 開発者が多い monoより.net Coreの方が検索でヒットする glade gtk3を利用したい .net Coreに対応しているから VisualSutdio風のIDEを使いたい Windowsで使い慣れているから IDEとフレームワークの種類 IDE SDK エディター デザイン デメリット 費用 MonoDevelop Mono VisaulStudioに慣れていると使いづらい GTK2デザイナー .net coreが利用できないっぽい? IDEの見栄えが悪い 無料 VisalStudio Code .net Core VisaulStudio風にカスタマイズすれば近づけるが手順が多く煩雑 抜けが発生する Gtk3 glade いろいろ拡張機能をインストール必要がある  無料 JetBrains Rider .net Core Mono Visual Studio風で使いやすい 便利な機能が既に組み込まれている Gtk3 glade 毎月料金が発生する 月1600円 開発環境がすぐ構築できそうななので Riderを利用することにした。 とりあえず試用期間30日を利用してみる JetBrains Riderをインストールする $ sudo snap install rider --classic Download Riderを起動する Ubuntuの環境でConsoleアプリの開発環境がすぐ出てくる Riderの日本語化 Settings/Preferences→Plugins→Marketplace より “japanese” を検索して見つかる”Japanese Language Pack”をインストールする 標準で.net Core がインストールされている。 三角のボタンをクリックするとビルドができます。 Nugetはどこにあるのか? 左下にありました。右クリックからソフトをインストールできます。 エラーが出たときに最適なライブラリをNugetから検索してくれる機能 ライブラリがインポートされていなくてエラーが出た時に、右クリックから最適なライブラリをNugetから検索してにいってくれます GladeとGTK3を組み合わせてGUIアプリを作る GTK+-3.0をインストール。 $ sudo apt install libgtk-3-dev GTK3デザイナ gladeをインストール。 $ sudo apt install glade GladeはGUIをデザインするソフトです。日本語にも対応しています。このGladeファイルと Riderと.net Coreを併用してGUIアプリを開発していきます。 続く GTKではじめるGUIアプリケーション開発
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

forとforeach、どちらのループを使用するのがいいのか考えてみる(現在List編は書きかけです…)

正直プログラム経験がまだまだ浅いから間違いはあるかも。ご指摘下さればありがたいです。 ※すみません。訳あってList編の途中のまま投稿しています。ご了承ください。月曜日にはまた時間が取れそうなので、月曜日には書き終える予定です。 きっかけ https://akinow.livedoor.blog/archives/52474053.html そもそものきっかけは、上記の記事でforeachの使用を控えるような内容を見たからだった。 (後々分かったが、これはUnity5.5で解決済みだった) 軽量化に力を入れていきたい身としては、重いとかメモリの効率が~なんて話は聞き捨てならない。 たとえ解決済みだとしても、気になる。一度気になりだしたら止まらない。 ・・・まあ、結果。ハマりましたよ。 見事にループ処理の沼にハマったので、同じような疑問を感じた人と、未来の自分用のメモとして、結局何がどういいのかまとめてみる。 コンパイル結果は、sharplab様にお世話になりました。 内部分のコードなんかはactual implementationを参照させていただきました。 配列編 コンパイル用に以下のようなコードを作成したので、コンパイルしてみる。 sumに配列の中身を足していく処理だ。 コンパイルしたいだけなので、sumがリセットされてないとか、配列に何も入っていないとか。 その辺は見逃してほしい・・・。 int[] TestArray = new int[100]; int sum = 0; //forの処理 for (int i = 0; i < TestArray.Length; i++) { sum += TestArray[i]; } //foreachの処理 foreach (int i in TestArray) { sum += i; } ↓ コンパイル int[] TestArray = new int[100]; int sum = 0; //forの処理 int num = 0; while (num < TestArray.Length) { sum += array[num]; num++; } //foreachの処理 int[] array2 = TestArray; int num2 = 0; while (num2 < array2.Length) { int num3 = array2[num2]; sum += num3; num2++; } 上記のような結果が出力された。 ・・・おんなじやん! 配列におけるループの内部処理は、単純にint型の変数を一つ用意して、ループ毎に足し、配列の大きさより大きくなったら、whileから抜けるだけのようだ。 まあ、強いて言えばここで配列が参照されているぐらいか。 int[] array2 = TestArray; ちなみに配列は参照型なので、コピーはされない。上記のように書いても、渡されるのは配列のアドレス情報のみ。この程度なら問題はない。 もしも知らない!気になった!って方はその辺は論点がずれてしまうので、以下サイト様がわかりやすいと思う。 https://ufcpp.net/study/csharp/oo_reference.html#type-category さて、結果としては、やってることが同じならforを使うメリットは無い。 そもそも、foreachは糖衣構文( 要は読み書きのしやすさを重要視されている処理 )だから、優先してforeachを使いたい。 List編 Listを回す際に、どちらを使用すべきか。 配列に対してのコンパイル結果は、分かりやすく非常に簡単な結果であったが、ListはEnumeratorというものが関わってくるため、少々厄介だったりする。 まずはコンパイルしてみる。内容はさっきと同じ。 ( ListにはLINQのCount()関数と、List内のcount変数があるが、変数でやる場合の方が個人的に多いし、こちらの方が処理が速いためこちらを使用している。他にも理由はあるけど後述する。) List<int> TestList = new List<int>(); int sum = 0; //forの処理 for (int i = 0; i < TestList.Count; i++) { sum += TestList[i]; } //foreachの処理 foreach (int i in TestList) { sum += i; } ↓ コンパイル List<int> list = new List<int>(); int sum = 0; //forの処理 int num = 0; while (num < list.Count) { sum += list[num]; num++; } //foreachの処理 List<int>.Enumerator enumerator = list.GetEnumerator(); try { while (enumerator.MoveNext()) { int current = enumerator.Current; sum += current; } } finally { ((IDisposable)enumerator).Dispose(); } ちょっとごちゃごちゃしてきたので、混乱するかもしれないが順番に紐解いていこう。 まず、forの部分は先ほどの配列とやり方は変わっていないので、言わなくても分かると思う。 問題はforeachの方だろう。 まず、Listは幾つかのインターフェースを継承しているのだが、以下の行でその中の一つであるIEnumerable内の唯一のメソッドであるGetEnumerator()というメソッドにアクセスしている。 List<int>.Enumerator enumerator = list.GetEnumerator(); GetEnumerator()が一体何をするメソッドなのか。 では、GetEnumerator()とはなんなのか。 簡単にまとめるとGetEnumerator()というメソッドは、List内にあるEnumeratorという構造体に自身の情報を設定して返すだけのメソッドだ。 もう少し詳しく言うならば、GetEnumerator()を呼び出した際に、Enumeratorのコンストラクタにアクセスし、渡されたList、初期index、渡された時点での_version、currentを設定し、設定したEnumeratorを返すメソッドだ。 IEnumerator内のメソッド 次に以下の処理に注目してもらいたい。 while (enumerator.MoveNext()) { int current = enumerator.Current; sum += current; } 次に疑問に思うのは、MoveNext()とは何者なのかという点だろう。 MoveNext()はEnumeratorが継承するインターフェースの一つであるIEnumerator内のbool型のメソッドだ。 こいつは簡単に言うと、List内の要素数が超えたらループを抜けて、要素を超えていない場合はループを続けるという処理が内部的に行われている。 もう少し深掘りしてみよう。 MoveNext()を覗いてみると、以下のようになっている。 public bool MoveNext() { List<T> localList = list; if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } これだけでは、訳が分からないかもしれないので、順番に見ていこう。 まず、以下の部分で、Listのバージョンが合っているか。現在探索している要素がList内の要素数を超えていないか。を調べている。 もし、条件が合っていた場合、current変数に現在の要素を設定し、探索位置をずらし、ループは続くのでtrueを返している。 if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } では、条件が満たされなかった場合はどうなるのか。 その場合は、MoveNextRare()に入る。 MoveNextRare()の処理は、まずversionが合っているか確認し、合っていなかった場合はエラーを吐く。 もしあっていた場合は、currentを初期化してfalseを返すことでループを終了します。 private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } これがだいたいのforeachのList探索の概要です。 では、結局どっちをつかうのがいいのか? 今一度、コンパイル結果を見てみよう。 List<int> list = new List<int>(); int sum = 0; //forの処理 int num = 0; while (num < list.Count) { sum += list[num]; num++; } //foreachの処理 List<int>.Enumerator enumerator = list.GetEnumerator(); try { while (enumerator.MoveNext()) { int current = enumerator.Current; sum += current; } } finally { ((IDisposable)enumerator).Dispose(); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

forとforeach、どちらのループを使用するのがいいのか考えてみた!

正直プログラム経験がまだまだ浅いから間違いはあるかも。ご指摘下さればありがたいです。 Qiitaに不慣れなため、見にくかったらごめんなさい! きっかけ https://akinow.livedoor.blog/archives/52474053.html そもそものきっかけは、上記の記事でforeachの使用を控えるような内容を見たからだった。 (後々分かったが、これはUnity5.5で解決済みだった) 軽量化に力を入れていきたい身としては、重いとかメモリの効率が~なんて話は聞き捨てならない。 たとえ解決済みだとしても、気になる。一度気になりだしたら止まらない。 ・・・まあ、結果。ハマりましたよ。 見事にループ処理の沼にハマったので、同じような疑問を感じた人と、未来の自分用のメモとして、結局何がどういいのかまとめてみる。 コンパイル結果は、sharplabにお世話になりました。 内部分のコードなんかはactual implementationを参照させていただきました。 配列編 コンパイル用に以下のようなコードを作成したので、コンパイルしてみる。 sumに配列の中身を足していく処理だ。 コンパイルしたいだけなので、sumがリセットされてないとか、配列に何も入っていないとか。 その辺は見逃してほしい・・・。 int[] TestArray = new int[100]; int sum = 0; //forの処理 for (int i = 0; i < TestArray.Length; i++) { sum += TestArray[i]; } //foreachの処理 foreach (int i in TestArray) { sum += i; } ↓ コンパイル int[] TestArray = new int[100]; int sum = 0; //forの処理 int num = 0; while (num < TestArray.Length) { sum += array[num]; num++; } //foreachの処理 int[] array2 = TestArray; int num2 = 0; while (num2 < array2.Length) { int num3 = array2[num2]; sum += num3; num2++; } 上記のような結果が出力された。 ・・・おんなじやん! 配列におけるループの内部処理は、単純にint型の変数を一つ用意して、ループ毎に足し、配列の大きさより大きくなったら、whileから抜けるだけのようだ。 まあ、強いて言えばここで配列が参照されているぐらいか。 int[] array2 = TestArray; ちなみに配列は参照型なので、コピーはされない。上記のように書いても、渡されるのは配列のアドレス情報のみ。この程度なら問題はない。 もしも知らない!気になった!って方はその辺は論点がずれてしまうので、以下サイト様がわかりやすいと思う。 https://ufcpp.net/study/csharp/oo_reference.html#type-category さて、結果としては、やってることが同じならforを使うメリットは無い。 そもそも、foreachは糖衣構文( 要は読み書きのしやすさを重要視されている処理 )だから、優先してforeachを使いたい。 List編 Listを回す際に、どちらを使用すべきか。 配列に対してのコンパイル結果は、分かりやすく非常に簡単な結果であったが、ListはEnumeratorというものが関わってくるため、少々厄介だったりする。 まずはコンパイルしてみる。内容はさっきと同じ。 List<int> TestList = new List<int>(); int sum = 0; //forの処理 for (int i = 0; i < TestList.Count; i++) { sum += TestList[i]; } //foreachの処理 foreach (int i in TestList) { sum += i; } ↓ コンパイル List<int> list = new List<int>(); int sum = 0; //forの処理 int num = 0; while (num < list.Count) { sum += list[num]; num++; } //foreachの処理 List<int>.Enumerator enumerator = list.GetEnumerator(); try { while (enumerator.MoveNext()) { int current = enumerator.Current; sum += current; } } finally { ((IDisposable)enumerator).Dispose(); } ちょっとごちゃごちゃしてきたので、混乱するかもしれないが順番に紐解いていこう。 まず、forの部分は先ほどの配列とやり方は変わっていないので、言わなくても分かると思う。 問題はforeachの方だろう。 まず、Listは幾つかのインターフェースを継承しているのだが、以下の行でその中の一つであるIEnumerable内の唯一のメソッドであるGetEnumerator()というメソッドにアクセスしている。 List<int>.Enumerator enumerator = list.GetEnumerator(); GetEnumerator()が一体何をするメソッドなのか。 では、GetEnumerator()とはなんなのか。 簡単にまとめるとGetEnumerator()というメソッドは、List内にあるEnumeratorという構造体に自身の情報を設定して返すだけのメソッドだ。 もう少し詳しく言うならば、GetEnumerator()を呼び出した際に、Enumeratorのコンストラクタにアクセスし、渡されたList、初期index、渡された時点での_version、currentを設定し、設定したEnumeratorを返すメソッドだ。 IEnumerator内のメソッド 次に以下の処理に注目してもらいたい。 while (enumerator.MoveNext()) { int current = enumerator.Current; sum += current; } 次に疑問に思うのは、MoveNext()とは何者なのかという点だろう。 MoveNext()はEnumeratorが継承するインターフェースの一つであるIEnumerator内のbool型のメソッドだ。 こいつは簡単に言うと、List内の要素数が超えたらループを抜けて、要素を超えていない場合はループを続けるという処理が内部的に行われている。 もう少し深掘りしてみよう。 MoveNext()を覗いてみると、以下のようになっている。 public bool MoveNext() { List<T> localList = list; if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } これだけでは、訳が分からないかもしれないので、順番に見ていこう。 まず、以下の部分で、Listのバージョンが合っているか。現在探索している要素がList内の要素数を超えていないか。を調べている。 もし、条件が合っていた場合、current変数に現在の要素を設定し、探索位置をずらし、ループは続くのでtrueを返している。 if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } では、条件が満たされなかった場合はどうなるのか。 その場合は、MoveNextRare()に入る。 MoveNextRare()の処理は、まずversionが合っているか確認し、合っていなかった場合はエラーを吐く。 もしあっていた場合は、currentを初期化してfalseを返すことでループを終了します。 private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } これがだいたいのforeachのList探索の概要です。 では、結局どっちをつかうのがいいのか? 以下のサイト様を見た所、速度面ではforの方がforeachよりも早い。 https://takap-tech.com/entry/2020/10/20/234610 メモリ面から見ても、GetEnumerator()でEnumeratorを生成している分、forの方がいいように感じられるが、私個人の見解としては、そこまで気にするほどではないように感じた。 よっぽど、速度を重要視する場合や、メモリ効率を考えたい!といった場合でない限りは、糖衣構文であるforeachの使用を避ける程ではないように思う。 まあ、この程度であれば好みの範疇かな? 結果 2021/04/21修正:配列の結果がforになってました!!!ごめんんささい!! 配列:foreach List:基本的にforeachでいい。よっぽど気にするようであればfor 以上! ご指摘、ご意見おまちしておりまs・・・ めtyくちゃ参考にさせていただきましたサイト様 https://qiita.com/NCT48/items/d8394d587e9fee969ca9
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WinFormsのシングルトンフォーム

publci class Form1 : Form { public Form1() { var button = new Button(){Text = "サブフォーム表示"}; button.Click += (s,e ) => SingleForm.Instance.Show(); Controls.Add(button); } } public sealed class SingleForm : Form { private static SingleForm instance; private SingleForm(){} public static SingleForm Instance { get { if (instance == null || instance.IsDisposed) instance = new SingleForm(); return instance; } } } 実行結果 サブフォームボタンをクリックすると、シングルフォームが表示されてない時だけ、新たに表示される。 シングルトンフォームで発生したイベントを呼び出し元フォームで受信する処理。 public partial class Form1 : Form { public Form1() { var button = new Button() { Text = "サブフォーム表示" }; button.Click += (s, e) => { //シングルトンフォームはShow()で表示する度に新しいインスタンスタが作られるので、 //作られる度に、新インスタンスのdelegateを参照しないといけない。 //シングルトンフォームがCloseされる時に破棄されるので -= は不要。 //ShowDialog() で表示する場合はフォームを閉じてもインスタンスが破棄されないので、-=が必要。 SingleForm.Instance.SubFormButtonClicked += (e) => MessageBox.Show(e.SubFormMessage,"親フォームで表示してるMessageBox"); SingleForm.Instance.Show(); }; Controls.Add(button); } } public sealed class SingleForm : Form { private static SingleForm instance; private SingleForm() { var button = new Button() { Width = 200, Text = "サブフォームのイベント発生ボタン" }; button.Click += Button_Click; Controls.Add(button); } public static SingleForm Instance { get { if (instance == null || instance.IsDisposed) instance = new SingleForm(); return instance; } } private void Button_Click(object sender, EventArgs e) { SubFormButtonClicked?.Invoke(new SubFormEventArgs("サブフォームから親フォームへのメッセージ")); } public delegate void SubFormEventHandler(SubFormEventArgs e); public event SubFormEventHandler SubFormButtonClicked; } public class SubFormEventArgs : EventArgs { public string SubFormMessage { get; private set; } public SubFormEventArgs(string msg) { SubFormMessage = msg; } } 実行結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む