20210829のC#に関する記事は8件です。

SOUND FORGE Pro Script - DTMプログラミング言語探訪

DTMプログラミング言語探訪 SOUND FORGE Pro Script 概要 波形編集ソフトを使う作業は定型的な操作が多いため、多くのソフトは手順を自動化するためのバッチ機能を搭載しています。また、そのうちいくつかのソフトはテキスト言語でバッチ処理を書くスクリプト機能を持っています。 SOUND FORGE Proもバッチ処理とスクリプトの両方を持っているので機能や手順を説明します。1 用途 定型的手順の自動化 複数ファイルの一括処理 バッチ処理 本題ではありませんが、スクリプトを使わないバッチ処理の手順も比較のために説明しておきます。 バッチジョブ作成 「ツール」メニュー → バッチコンバータ を選択してバッチコンバータウィンドウを表示します。 「プロセス」タブを選択します。 「選択」ドロップダウンで適用するエフェクトを指定して「エフェクトの追加」ボタンを押します。 エフェクトのパラメータ設定画面が出るので、パラメータを調整します。 プリセット名(ここではmy_preset)を指定してFDアイコンを押して保存、OKボタンを押して閉じます。 同様にいくつかエフェクトを追加したらバッチコンバータウィンドウのFDアイコンからバッチジョブを保存します。 バッチジョブ実行 バッチコンバータウィンドウのフォルダーアイコンからバッチジョブファイルを開きます。 「変換するファイル」タブから対象ファイルまたは対象フォルダを追加します。 「保存」タブから保存オプションの「ファイルの保存先」を指定します。 「ジョブの実行」ボタンで実行します。 言語仕様 C#/JScript/VBScript Sound Forge API .NET Framework .NET Frameworkを使用しておりC#/JScript/VBScript言語で記述することができます。サンプルスクリプトはC#で書かれているのでそれに倣うのが良いと思います。 Sound Forge APIの完全な公式リファレンスはネット上にはありません。「ヘルプ」メニューの「目次と索引」から見ることができます。 C#.NETのAPIを呼ぶことができます。そのためディレクトリやファイルの操作やSystem.Windows.FormsのMessageBox.Show()を使ったメッセージダイアログ表示も可能です。 プログラム例 シンプルな例 開いているファイルにノーマライズと自動トリム(前後の空白を削除)をおこなうスクリプトです。 EntryPointクラスのFromSoundForge()メソッドに処理を書きます。 IScriptableApp::DoMenuAndWait()メソッドはアプリのメニュー項目を実行します。 これだけで簡単なバッチ処理を書けますが、各処理の細かいパラメータを指定できないのが問題です。 DoMenuAndWait()の第2引数をtrueにするとデフォルト値で処理されます。デフォルト値の詳細はドキュメントを見ても不明です。第2引数がfalseの場合はパラメータを設定するためのウィンドウが都度表示されるので好きな値を設定できますが、多くのファイルを一気に処理するような用途には向きません。 NormalizeTrim.cs using System; using SoundForge; public class EntryPoint { public void FromSoundForge(IScriptableApp app) { app.SetStatusText("Script Start"); app.DoMenuAndWait("Process.Normalize", true); app.DoMenuAndWait("Process.AutoTrimCrop", true); app.SetStatusText("Script End"); } } また、DoMenuAndWait()の第1引数に与えるメニュー名の一覧がドキュメントにないことも問題です。SOUND FORGE Proは言語設定を簡単に変更することができず、日本語版を購入した人は英語メニュー名がわかりません。 ネット上のドキュメントから英語メニュー名を確認することでなんとか対応可能です。メニュー名が間違っていると実行時エラーになるのでエラーが出なければ正しいメニュー名です。 なお、ネット上のドキュメントにはスクリプトのAPIの説明はありません。 実用的な例 前項のパラメータ問題を解決し、複数のファイルにノーマライズと自動トリムをかけるスクリプトです。 入力ファイルのフォルダと出力先のフォルダをスクリプトに指定しています。対象フォルダにある.wavファイルをすべて処理します。 ISfFileHost::DoEffect()を使うことで好みのパラメータが設定されたプリセットを指定しています。ここではノーマライズ処理のプリセットとしてmy_normalize_preset、トリム処理のプリセットとしてmy_trim_presetを指定しています。 NormalizeTrimBatch.cs using System.IO; using SoundForge; public class EntryPoint { public void FromSoundForge(IScriptableApp app) { string inPath = @"D:\work\samples\"; string outPath = @"D:\work\processed\"; string targetFile = "*.wav"; DirectoryInfo dir = new DirectoryInfo(inPath); FileInfo[] files = dir.GetFiles(targetFile, SearchOption.TopDirectoryOnly); foreach (FileInfo file in files) { app.OutputText(inPath + file.Name); ISfFileHost inFile = app.OpenFile(inPath + file.Name, true, true); if (inFile == null) return; ISfFileHost outFile = app.NewFile(inFile.DataFormat, false); outFile.ReplaceAudio(new SfAudioSelection(0, 0), inFile, new SfAudioSelection(inFile)); outFile.DoEffect("Normalize", "my_normalize_preset", new SfAudioSelection(outFile), EffectOptions.EffectOnly | EffectOptions.WaitForDoneOrCancel); outFile.DoEffect("Auto Trim/Crop", "my_trim_preset", new SfAudioSelection(outFile), EffectOptions.EffectOnly | EffectOptions.WaitForDoneOrCancel); outFile.SaveAs(outPath + file.Name, inFile.SaveFormat.Guid, "Default Template", RenderOptions.RenderOnly); outFile.WaitForDoneOrCancel(); inFile.Close(CloseOptions.DiscardChanges); outFile.Close(CloseOptions.DiscardChanges); } } } プリセットの作成は、何か波形ファイルを読み込んだ状態で、たとえば「プロセス」メニュー → ノーマライズを選択し、パラメータを調整した後に上部のテキストボックスにプリセット名を入力しFDアイコンを押して保存することでおこないます。 スクリプトの使用方法 スクリプト作成 「表示」メニュー → スクリプトエディタ でスクリプトエディタを表示させます。 エディタでスクリプトを入力します。 スクリプトエディタから実行 左端のフォルダアイコンはスクリプトの読み込み。サンプルスクリプトが用意されているので参考になります。 隣のFDアイコンは保存と別名保存。 中央はスクリプトテンプレートを読み込みます。最初はこれを使うと楽かもしれません。 右端の二つは実行とコンパイル。 メニューから実行 スクリプトは特に変えなければ(インストールフォルダ)\SOUND FORGE\Script Menu\ に保存されます。 ここに保存したスクリプトは「ツール」メニュー → スクリプトの作成 から実行できるようになります。 コマンドラインから実行 以下のようにしてコマンドラインから実行することもできます。バージョンによってファイル名が異なるので注意してください。コマンドラインから引数を渡す仕組みも用意されています。 > "C:\Program Files\SOUND FORGE\ForgeXXX.exe" -SCRIPT:"C:\Program Files\SOUND FORGE\Script Menu\my_convert.cs" 感想 昔から定番波形編集ツールとして使われてきたSOUND FORGE Proですが、スクリプト機能はあまり使われていないのか情報が整理されていないのが今回調べて少し意外に感じました。 C#言語は良い言語ですが、IDEからクラス定義などをたどれないとつらい部分もあります。内蔵のスクリプトエディタは機能が貧弱なので、スクリプトの下書きをVisual Studioで書くことでシンタックスハイライトの恩恵を受けながら入力するというような裏技もあります。このときForgeXXX.Script.dllを参照に含めることでAPI定義も参照することができて少し快適になります。 iZotope RXの人気に押されて、最近は少し古く感じることもあるSOUND FORGE Proですが今も安定して使いやすく、今回調査した波形編集ソフトのスクリプトでは(ドキュメント不足を除くと)一番こなれている印象でした。 DTMプログラミング言語探訪 Windows版のSOUND FORGE Pro 12で動作確認しました。最新版の15でも大きな変更はないと思います。Mac版がどこまでスクリプトに対応しているのかは未確認です。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【UniRx】Subjectをコンポーネントのインスタンス変数にした場合の寿命

はじめに Component.UpdateAsObservable()等のUniRxの拡張メソッドは、そのコンポーネントが付与されたGameObjectのDestroyのタイミングでComplete状態に移行するようになっています。 このため、以下のようにAddTo(this)は不要です。 public class SomeComponent : MonoBehaviour { private void Start() { this.UpdateAsObservable() .Subscribe(_ => DoSomething()); // .AddTo(this) // ↑ GameObjectのDestroyでCompleteになるのでAddTo(this)は不要 } } では自作のSubjectはどうなのでしょうか? ストリーム停止条件を記述しない場合 ストリーム停止条件を記載しない書き方 public class EventFiringComponent : MonoBehaviour { private readonly Subject<bool> someSubject = new Subject<bool>(); public IObservable<bool> SomeObservable => this.someSubject; } public class SubscribingComponent : MonoBehaviour { public void SubscribeEvents(EventFiringComponent component) { component.SomeObservable.Subscribe(_ => DoSomething()); } } まずはシンプルに書いた場合の挙動を考えます。 この場合、ストリームが停止するのは、Subjectを持つコンポーネントが付与されたGameObjectがDestroyされた後にGCが走るタイミングとなります。 それはDestroy後すぐかもしれないし、アプリケーション終了のタイミングかもしれません。 メモリ使用制限が厳しい実行環境ではこれがネックとなる可能性はありますが、記述量の少なさや装飾が少ないことによる可読性の高さといった利点を考えると、この書き方を採用する場面も往々にしてあると思います。 コンポーネントと寿命を同じにする方法 Component.UpdateAsObservable()等と同じようにコンポーネントの付与されたGameObjectのDestroyでストリームが停止するようにするためには、以下のように書くことになります。 ストリームソースの寿命をコンポーネントに合わせる書き方 public class EventFiringComponent : MonoBehaviour { private readonly Subject<bool> someSubject = new Subject<bool>(); public IObservable<bool> SomeObservable => this.someSubject; private void OnDestroy() { this.someSubject.OnCompleted(); } } public class SubscribingComponent : MonoBehaviour { public void SubscribeEvents(EventFiringComponent component) { component.SomeObservable.Subscribe(_ => DoSomething()); } } これでDestroyのタイミングでストリームソースがComplete状態に移行することになり、Component.UpdateAsObservable()等と同じライフサイクルになります。 「UniRxの拡張メソッドとライフサイクルが同じ」、「コンポーネントの寿命とストリームソースの寿命は同じ」という仕様のわかりやすさは非常に魅力的です。 また、UniRx内ではComplete状態になると不要になった内部の参照を切るように工夫されており、メモリ効率が上がることも期待できます。 しかし、1行のみとは言え、Subjectを多く保有しているクラスでは記述量の増加が結構辛いのと、宣言箇所と停止条件を書く場所が離れるので、記述漏れが発生しそうで怖いです。 シーン内の一部品では過剰設計となるかもしれませんが、実行環境のメモリ使用制限が厳しい場合やフレームワークなどの汎用部品では使えそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Entity Framework Core で移行したあと、sqlcmdを使ってSQL Serverを観察する

この記事は SQL Server (Docker版)をデータベースファーストで操作してみた記事の続き 前回は、既存のデータベースからモデル(DbContextを継承したクラス)を生成する、いわゆるスキャフォールディングを試した (スキャフォールディングについて書かれた、マイクロソフトのよくわかる記事) 今回はモデルからデータベースを生成する方法、いわゆるコードファーストな方法を試す EF Core で何ができるか 公式ドキュメントから一部引用させていただく EF は、次のモデル開発アプローチをサポートしています。 既存のデータベースからモデルを生成する。 データベースに合わせてモデルのコードを手動で書く。 モデルが作成されたら、EF の移行 を使用して、モデルからデータベースを作成します。 移行により、モデルの変更に応じてデータベースを進化させることができます。 引用元:https://docs.microsoft.com/ja-jp/ef/core/#the-model EF Core 個人的ロードマップ 「既存のデータベースから~」は前回やった 今回はモデルのコードを手動で書いて、そこから移行(Migration)を使ってデータベースをつくるところまでをでやりたい 次回以降があれば、データベースを進化させるを試したい 移行によりDBがどう変化していくか など ソリューションにライブラリプロジェクトを追加する さっそく作業を始める まず最初に、EF Core ドキュメントを参考にして、ライブラリプロジェクトIntroをつくる 今回は、主にこのプロジェクトでの作業となる スタートアッププロジェクトは、前回記事からの続きでWPF_EFCoreをつかう (※ 前回記事ではプロジェクト名まで言及してないかも・・・) 開発者用パワーシェル # プロジェクト作成 dotnet new classlib --framework "netcoreapp3.1" -o Intro # ライブラリプロジェクトをスタートアッププロジェクトの参照に追加 dotnet sln add .\Intro\Intro.csproj dotnet add .\WPF_EFCore\WPF_EFCore.csproj reference .\Intro\Intro.csproj # ライブラリプロジェクトに必要なパッケージをインストール dotnet add .\Intro\Intro.csproj package Microsoft.EntityFrameworkCore dotnet add .\Intro\Intro.csproj package Microsoft.EntityFrameworkCore.SqlServer プロジェクトにインストールしたパッケージを確認する WPF_EFCoreおよびIntroのどちらも .NET Core 3.1 であり、EF Core 5系 が入っている 開発者用パワーシェル dotnet list package # プロジェクト 'WPF_EFCore' に次のパッケージ参照が含まれています # [netcoreapp3.1]: # 最上位レベル パッケージ 要求済み 解決済み # > Microsoft.EntityFrameworkCore.Sqlite 5.0.9 5.0.9 # > Microsoft.EntityFrameworkCore.SqlServer 5.0.9 5.0.9 # > Microsoft.EntityFrameworkCore.Tools 5.0.9 5.0.9 # > Prism.Unity 8.0.0.1909 8.0.0.1909 # # プロジェクト 'Intro' に次のパッケージ参照が含まれています # [netcoreapp3.1]: # 最上位レベル パッケージ 要求済み 解決済み # > Microsoft.EntityFrameworkCore 5.0.9 5.0.9 # > Microsoft.EntityFrameworkCore.SqlServer 5.0.9 5.0.9 なぜ .NET5 ではないのか? Prism Template Pack から作った空のプロジェクトのデフォルトが .NET Core 3.1 であり そこから .NET 5 に変更するのを忘れたため Introプロジェクトにモデルを追加する 公式ドキュメントのモデルを参考にして、Introプロジェクトにクラスを3つ追加する 接続文字列を自分の環境にあわせる BloggingContextのOnConfiguringメソッドを一部変更する SQL Server DockerコンテナのBloggingデータベースに接続する BloggingContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer( + "Data Source=\"localhost, 11433\";Initial Catalog=Blogging;User ID=sa;Password=SqlPass1234"); - @"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); } 接続文字列がベタ書きであることはいけない気がするも、ベストプラクティスが分からない&テストのため、こう書いている SQLサーバーをたてる コードファーストで書いたモデルをDBに移行するために 移行先となる Docker コンテナの SQL サーバーを起動する Powershell # コンテナを起動する docker start sql1 sql1については、関連する過去の記事を参照 EF Core ツールのコマンドを使って移行する コードファーストでSQLサーバーに新しいDBをつくっていく 具体的には、Visual Studio のパッケージマネージャーコンソールから、EF Core ツールのコマンドを打っていく作業となる 移行というのが、モデルからDBを作成、または変更していくことを表している 前準備 リファレンスを準備して、共通パラメータを確認する EF Core コマンドツールのドキュメント コマンドは10個くらいあるらしい 今回は移行に関するものだけを使う 共通パラメータ 共通パラメータである-Projectを省略すると パッケージマネージャーコンソール の 既定のプロジェクト がターゲットプロジェクトとして使用されます とのこと。 毎回プルダウンから既定のプロジェクトを選びなおしてもいいし あるいは毎回-Projectを指定してもいい また、-V or -Verbose パラメータをつけると、実行中の詳細情報を見ることができる コマンドからエラーが返ってきたときには、エラーの原因を探るために-Vをつけて再度コマンドを打つ 〈本題〉モデルを移行する 3つのコマンドを順番に打って、モデルをDBに移行する Get-DbContext ・・・使用可能なDbContextを取得する Add-Migration ・・・移行を追加する Update-Database ・・・移行をデータベースに反映する 2つ目の「移行を追加する」のイメージが難しいが モデルの変更履歴をセーブポイントとしてローカルに残していくイメージに近いと思う セーブポイントを実際にDBへ反映させるのが、3番目のアップデートになる Get-DbContext 使用可能なDbContextを取得する パッケージマネージャーコンソール Get-DbContext -Project Intro -StartupProject WPF_EFCore # Build started... # Build succeeded. # MyDockerDBContext # BloggingContext スタートアッププロジェクトのMyDockerDBContextと、新規追加したプロジェクトのBloggingContextの2つのコンテキストが表示された 今回使うのは、ライブラリプロジェクトに追加したBloggingContextとなる 以後、-ContextパラメータをつかってBloggingContextを毎回指定する (指定しなかった場合の挙動について、次節で言及する) 〈参考〉Get-DbContextのパラメータを未指定としたときの動作 パラメータを省略すると、スタートアッププロジェクトにあるMyDockerDBContextだけが表示される スタートアッププロジェクトから参照されるライブラリプロジェクトのBloggingContextは表示されない パッケージマネージャーコンソール Get-DbContext # Build started... # Build succeeded. # MyDockerDBContext Add-Migration 移行を追加する 最初の移行は、DBの新規作成に相当する その次からの移行は、1つ前の移行からの差分が記録される パッケージマネージャーコンソール Add-Migration -Name Init -Context BloggingContext -Project Intro -StartupProject WPF_EFCore # Build started... # Build succeeded. # To undo this action, use Remove-Migration. Introプロジェクトに新しくMigrationsフォルダが生成される 以後、Add-Migrationをするたびに、移行履歴が記録されていく 移行の挙動については、また別の記事で試す予定 DbContextが複数ある場合は-Contextを省略してはいけない Get-DbContextで確認したときにDbContextが2個あったので -Contextパラメータで使うコンテキストを指定しないと、エラーが発生する パッケージマネージャーコンソール # -Context 引数を省略した Add-Migration -Name Init -Project Intro -StartupProject WPF_EFCore More than one DbContext was found. Specify which one to use. Use the '-Context' parameter for PowerShell commands and the '--context' parameter for dotnet commands. 〈意訳〉 2つ以上のDbContextが見つかったよ。どっちを使うか決めてね。Powershellをつかっている人は-Contextパラメータを使うといいよ。 Update-Database 移行をデータベースに反映する 何も指定しなければ、最後の移行が反映される パッケージマネージャーコンソール Update-Database -Project Intro -Context BloggingContext # Build started... # Build succeeded. # Applying migration '202108********_Init'. Add-Migrationを繰り返すと移行先が複数になる 移行先を選択したい場合は、-Migrationで指定する パッケージマネージャーコンソール Update-Database -Project Intro -Context BloggingContext -Migration Init -StartupProject WPF_EFCore # Build started... # Build succeeded. # No migrations were applied. The database is already up to date. # Done. すでに移行が完了しているので、「DBはup to dateされている」と表示された データベースを確認する ここまでの変更がDBに反映されていることを確認する SQL Server (Dokcerコンテナ)に入る 移行作業の前にsql1(Docker版SQLサーバー)をスタートさせているので、その中に入る Powershell # コンテナに入る docker exec -it sql1 "bash" SQL Serverにログインする localhostにユーザIDsaでログインする パスワードを聞かれるので入力する bash # DBサーバーにログインする。 /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -W -Wオプションをつけると、画面への出力が整形されてイイ感じになる(参考にさせていただいた記事) DB一覧を取得する 既存のDBに加えて、Bloggingデータベースが新たに追加されている sqlcmd 1> SELECT name FROM sys.databases; 2> GO name ---- master tempdb model msdb + Blogging 作業するデータベースをBloggingにする 作業するデータベースをUSEで指定する 使うのはもちろんBloggingデータベース sqpcmd 1> USE Blogging; 2> GO Changed database context to 'Blogging'. 今いるデータベースの名前を取得すると、Bloggingと表示される sqpcmd 1> SELECT DB_NAME(); 2> GO - Blogging ちなみに自分の環境では、デフォルトはmasterだった テーブル一覧を取得する TYPE = 'U'のテーブル一覧を取得する TYPEを指定しないと、たくさんのテーブルが表示される sqpcmd 1> SELECT * FROM sys.objects WHERE TYPE = 'U'; 2> GO name object_id principal_id schema_id parent_object_id type type_desc create_date modify_date is_ms_shipped is_published is_schema_published ---- --------- ------------ --------- ---------------- ---- --------- ----------- ----------- ------------- ------------ ------------------- __EFMigrationsHistory 885578193 NULL 1 0 U USER_TABLE ... Blogs 917578307 NULL 1 0 U USER_TABLE ... Posts 949578421 NULL 1 0 U USER_TABLE ... 3つのテーブルが表示された BlogsテーブルとPostsテーブルは 移行した際にコンテキストに含まれていたDbSet<Blog> BlogsとDbSet<Post> Postsである __EFMigrationsHistoryは、その名からして、移行履歴を保持しているテーブルらしい __EFMigrationsHistoryの中を覗いてみる 1> SELECT * FROM __EFMigrationsHistory; 2> GO MigrationId ProductVersion ----------- -------------- 202108********_Init 5.0.9 (1 rows affected) MigrationIdとProductVersionの2つのフィールドが存在する MigrationIdはAdd-Migrationしたときに生成された.csファイルの名前と一致しており 一方のProductVersionは EF Core のバージョンと一致している テーブルのフィールドを確認する 1> SELECT * FROM Blogs; 2> GO BlogId Url Rating ------ --- ------ Blogsテーブルには、モデルで指定した3つのフィールドが存在する List<Post>のPostsフィールドは存在しない 1> SELECT * FROM Posts; 2> GO PostId Title Content BlogId ------ ----- ------- ------ Postsテーブルには、モデルで指定した4つのフィールドが存在する Blog型のBlogフィールドは存在しない いずれれもstringやintなどのプリミティブ型のみがテーブルのフィールドに反映されて ListやBlogなどの集約関係や実態などはテーブルには反映されていないことがわかる マイグレーションしたときのコード Initクラスから一部抜粋 protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Blogs", columns: table => new { BlogId = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Url = table.Column<string>(type: "nvarchar(max)", nullable: true), Rating = table.Column<int>(type: "int", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Blogs", x => x.BlogId); }); migrationBuilder.CreateTable( name: "Posts", columns: table => new { PostId = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Title = table.Column<string>(type: "nvarchar(max)", nullable: true), Content = table.Column<string>(type: "nvarchar(max)", nullable: true), BlogId = table.Column<int>(type: "int", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Posts", x => x.PostId); table.ForeignKey( name: "FK_Posts_Blogs_BlogId", column: x => x.BlogId, principalTable: "Blogs", principalColumn: "BlogId", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_Posts_BlogId", table: "Posts", column: "BlogId"); } おわりに 今回は、モデルからDBを作成するとこまでを試した 次は、実際にデータを入れて、さらにテーブルの構造を変えてみたりして EF Core & SQL Serverの挙動を理解したい SQL Serverの操作に関して、参考にさせていただいた記事 関連する過去の記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

永続化としてのシリアライズ

モデル永続化の課題 アプリケーションの実行時・終了時問わず、モデルの状態を保存しない日はありません。 その際、サーバーアプリケーションなどでは最終的にデータベースに保存するのが定石ですが、デスクトップアプリなどでは簡易的にファイルに保存することがポピュラーだと思います。 ファイルに保存する場合、インスタンスをファイル保存可能な形式(バイナリ, XML, JSON, etc...)にシリアライズすることになりますが、これがなかなか曲者です。 単純なプリミティブ型で表せるものであれば適当な既定のシリアライザを使えばいいですが、アプリケーション内のモデル全般となると簡単ではありません。デフォルトコンストラクタのないクラスや、privateのフィールド、public setterのないプロパティといった、既定のシリアライザでは対応しないものも扱います。特にDDDなんかをやっていると、publicのsetterはほとんど見かけなくなりしばしばこの問題に突き当たる気がします。 そんなとき、シリアライズ用のDTOを作るのは悪手です。DTOとモデルの変換は定型コードの大量生産になり、うんざりすることが目に見えています。リファクタリングでモデルが変われば変換コードでバグもでるでしょう。できればモデルのインスタンスをそのままシリアライズしたい。 今回のシリアライズ対象のクラス 最初にテスト用のクラスを示します。これをシリアライズ・デシリアライズして元の状態を復元できることが目標です。 class Serialized { private string field; public string GetFieldProperty => this.field; public string AutoProperty { get; set; } public string AutoPropertyWithoutPublicSet { get; private set; } public Serialized(string field) { this.field = field; } public void Set(string s) { this.AutoPropertyWithoutPublicSet = s.ToUpper(); } } テストデータとして下記初期化を行ったインスタンスを使います。 var s = new Serialized("FieldValue") { AutoProperty = "AutoPropertyValue" }; s.Set("WithoutSetValue"); DataContract なんとなく、不遇な扱いを受けている印象のあるDataContractですが、こういう需要にぴったりだったりします。MSDocsのシリアル化のガイドラインでも 使用する型のインスタンスを Web サービスで永続化させる、または使用する必要がある場合は、データ コントラクトのシリアル化 をサポートすることを検討してください。 とありますが、実際今回の需要にはぴったりです。 まずテストクラスを次のように書き換えます。 [DataContract] class Serialized { [DataMember] private string field; public string GetFieldProperty => this.field; [DataMember] public string AutoProperty { get; set; } [DataMember] public string AutoPropertyWithoutPublicSet { get; private set; } // 以下略 } シリアライズ化したいデータにのみDataMemberAttributeをつけます。GetFieldPropertyプロパティについては、フィールドのほうを直接シリアライズしているのでスルーします。 続いてDataContractSerializerを使ってシリアライズ var serializer = new DataContractSerializer(typeof(Serialized)); serializer.WriteObject(anystream, t); シリアライズ結果は <Serialized xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <AutoProperty>AutoPropertyValue</AutoProperty> <AutoPropertyWithoutPublicSet>WITHOUTSETVALUE</AutoPropertyWithoutPublicSet> <field>FieldValue</field> </Serialized> となりAttributeで設定した通りです。コードは省略しますが、デシリアライズもきちんと動作します。 長所 .NET組み込み モデルのバージョンアップにも対応(データ コントラクトのバージョン管理) JSON形式も可(DataContractJsonSerializer) コンストラクタが呼び出されない。DataContextではコンストラクタの代わりにFormatterServices.GetUninitializedObjectを使います。オブジェクトのライフサイクルとして、一度生成され、保存され、復元されるものがコンストラクタを再び呼ばないのはとても自然 短所 モデルの変更が必要。とはいえドメインロジックやインタフェースに影響する変更じゃないので許容範囲か 特定の技術にモデルが依存する。とはいえドメインロジックやインタフェースに影響する変更じゃないので許容範囲か フィールド・プロパティを追加したときにうっかりDataMemberAttributeを付け忘れるとシリアライズされない。当然テストで見つけてください Json.NET その① DataContractでできることはJson.NETでもだいたいできます(それがDataContract不遇の理由だったりして…)。まずクラスを書き換えます。 [JsonObject] class Serialized { [JsonProperty] private string field; [JsonIgnore] public string GetFieldProperty => this.field; [JsonProperty] public string AutoProperty { get; set; } [JsonProperty] public string AutoPropertyWithoutPublicSet { get; private set; } // 以下略 シリアライズ結果は { "field": "FieldValue", "AutoProperty": "AutoPropertyValue", "AutoPropertyWithoutPublicSet": "WITHOUTSETVALUE" } となります。JsonIgnoreAttributeをつける理由は、Json.NETがデフォルトでopt-outな挙動をするから。つまりpublicなプロパティは全部出力します。その挙動を変えたいときにはJsonObjectAttributeにMemberSerialization.OptInを渡しましょう(参考)。コードは省略しますが、デシリアライズもきちんと動作します。 長所 ライブラリとして非常にポピュラー モデルのバージョンアップにも対応(JsonExtensionDataAttribute) 短所 モデルの変更が必要。とはいえドメインロジックやインタフェースに影響する変更じゃないので許容範囲か 特定の技術にモデルが依存する。とはいえドメインロジックやインタフェースに影響する変更じゃないので許容範囲か フィールドを追加したときにうっかりJsonPropertyAttributeを付け忘れるとシリアライズされない。当然テストで見つけてください プロパティを追加したときにうっかりJsonIgnoreAttributeを付け忘れるとシリアライズされてしまう。でもこれはデシリアライズ時に無視されるだけなのであまり悪影響はないかも コンストラクタが呼び出される。すべての引数をdefault値で呼び出します。なのでコンストラクタ内でnullチェックとかしてると例外が出ます。限定的な回避策としては、 コンストラクタの引数名とプロパティ名を同じにしておく。そうすればデシリアライズ時に、コンストラクタ引数としてそのプロパティの値を渡してくれます。 JsonConstructorAttributeをつけたprivateコンストラクタを作っておく。これはpublicにするとモデルのインタフェースが変わってしまうprivateにしておきます(参考)。 Json.NETが内部でインスタンス生成する仕組みをoverride。これはあまり詳しく見てないですが、可能なようです Json.NET その② 要は、インスタンスのデータを保存したいんですから、フィールドを全部シリアライズできればいいんですよ。自動実装プロパティにも、コンパイラが自動生成したフィールドがあるじゃないですか。 ということでテストクラスを書き換えます。 [Serializable] class Serialized // 以下略 SerializableAttributeをつけるだけです。あとはSerializerのほうで var serializer = new Newtonsoft.Json.JsonSerializer { ContractResolver = new DefaultContractResolver { IgnoreSerializableAttribute = false, }, }; IgnoreSerializableAttributeをfalseにします。シリアライズ結果がこちら。 { "field": "FieldValue", "<AutoProperty>k__BackingField": "AutoPropertyValue", "<AutoPropertyWithoutPublicSet>k__BackingField": "WITHOUTSETVALUE" } コンパイラが自動生成したBackingFieldがきっちり出力されています。Reflectionを使うと実行時にこれらのフィールドが取得できちゃうんですね。それを出力すれば万事解決、フィールドをラップしているプロパティとか余計なことを考えなくて済む! ちなみにこの方法はJson.NETのソースコードを読んでいて知った方法で、使用方法を説明するドキュメントは見つけられませんでした。一番近いのはMemberSerializationの"Fields"の説明だと思います。 コードは省略しますが、デシリアライズもきちんと動作します。 長所 モデルの修正量が少ない。SerializableAttributeならそれほど不自然じゃないし特定技術に依存しているわけでもない モデルのフィールド・プロパティが増えてもAttributeとかの追記漏れがない 短所 フィールド名やプロパティ名が変わると、モデルとシリアライズ結果に互換性がなくなる。前2つの方法では、DataMemberAttributeのName引数やJsonPropertyAttributeのPropertyName引数で、シリアライズ時のプロパティ名を変更し、名前変更の影響を吸収できます。今回の方法でも、C# 7.3以降、自動実装プロパティのBackingFieldにAttributeをつけることで回避できますが、そこまでするなら前2つの方法でいいかなという気がします 自動実装プロパティのBackingFieldの命名方法が変わるとモデルとシリアライズ結果に互換性がなくなる。これは完全にコンパイラ依存なので何も保証が効きません 他のシリアライザ その他シリアライザについてちょっと見た限りだとあまりいい方法はなさそうです。 System.Text.Json: privateなフィールドを出力できない? System.Xml.Serialization.XmlSerializer: privateなフィールドを出力できない? なにかわかったら追記します。 その他 他クラスのインスタンスをフィールド・プロパティとして持っている場合、今回のやり方は再帰的に適用されます。 サードパーティのクラスを内部で使っていたらどうすればいいのか。それは諦めます。カプセル化されていて実装が隠蔽されている以上、シリアライズは不可能です。それこそがカプセル化だから。最低限復元可能にするためのデータを自前で保存するとかになると思います まとめ 最初の要件を満たせる範囲だと3つ方法が見つかりましたがどれも一長一短となりました。個人的にはDataContractがトータル悩むことが少なくなりそうだなと印象です。 DDDのリポジトリにファイルを使う場合、トランザクション管理とかどうすればいいのかな…というのが次の悩み(ファイルを使うこと自体が正しいかはありますが…) 参考文献 シリアル化のガイドライン データ コントラクトのバージョン管理 Json.NET Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCode+C#でタスクトレイ常駐アプリを作りたい

はじめに Windows環境で、簡単なタスクトレイ常駐アプリを作ることになったが、このためだけに Visual Studio をインストールするのが面倒臭い・・・ということで、Visual Studio ではなく VSCode でやってみた。 環境 .Net Core 3.1.412 Visual Studio Code 1.59.1 プロジェクト作成 まずは任意のプロジェクトフォルダを作成し、そこで VSCode を起動し、ターミナルから dotnet new wpf でプロジェクトを作成する。 以下のように、WPFのテンプレートとなるファイルが作成される。 デバッグ実行 とりあえずデバッグ実行できるか試してみる。 左のデバッグから「launch.jsonを作成します」→「.Net Core」を選択する。 以下のような launch.json ファイルが作成されるはず。 (「実行とデバッグ」→「.Net Core」としてしまうと、空の launch.json が作成されてしまうらしい。  この場合は、いったん launch.json を削除して、「launch.jsonを作成します」→「.Net Core」をやり直すとよい。) 左上の実行とデバッグボタン(▶ボタン)を押すと、WPFのテンプレート画面が表示される。 起動時は画面を非表示にする 常駐アプリなので、起動時の画面表示は不要。 App.xamlの StartupUri="MainWindow.xaml" の部分を削除する。 App.xaml <Application x:Class="ResidentProgramSample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ResidentProgramSample" StartupUri="MainWindow.xaml">  ★ここの設定を削除 <Application.Resources> </Application.Resources> </Application> プロジェクトファイルの設定 プロジェクトファイル(プロジェクト名.csproj)に参照設定を追加する。 今回は、タスクトレイのアイコンを使用するのでそのアイコンファイルへの参照と、System.Windows.Formsへの参照を追加する。 ResidentProgramSample.csproj <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> <UseWPF>true</UseWPF> <UseWindowsForms>true</UseWindowsForms>    ★System.Windows.Forms を使用するために追加 </PropertyGroup> <ItemGroup> <Resource Include="icon.ico" />         ★アイコン を使用するために追加 <Reference Include="System.Windows.Forms" />  ★System.Windows.Forms を使用するために追加 </ItemGroup> </Project> ※ icon.icoファイルの実体は適当に作成してください。 ※ System.Windows.Formsを使用しないなら、この設定は不要です。 タスクトレイへのアイコン、コンテキストメニューの設定 OnStartupで、タスクトレイへアイコンやコンテキストメニューを登録し、Thread.Start()で定周期処理を起動する。 App.xaml.cs protected override void OnStartup(StartupEventArgs e) { Debug.WriteLine("OnStartup"); base.OnStartup(e); var icon = GetResourceStream(new Uri("icon.ico", UriKind.Relative)).Stream; var menu = new System.Windows.Forms.ContextMenuStrip(); menu.Items.Add("終了", null, Exit_Click); notifyIcon = new System.Windows.Forms.NotifyIcon { Visible = true, Icon = new System.Drawing.Icon(icon), Text = "ResidentProgramSample", ContextMenuStrip = menu }; notifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(NotifyIcon_Click); // 定周期処理の開始 this.thread = new Thread(intervalProcess); this.thread.Start(); } ※ intervalProcessが別スレッドで起動されるので、この中に定周期処理を記述する。 ※ アイコンクリックでNotifyIcon_Clickが、右クリックから終了でExit_Clickがコールされる。 設定画面 定周期処理の設定ができるよう、MainWindow.xamlを設定画面として利用する。 以下のように、xml形式で画面のアイテムを配置する。 MainWindow.xaml <Window x:Class="ResidentProgramSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ResidentProgramSample" mc:Ignorable="d" xmlns:wfm="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" Title="設定" Height="233" Width="626" WindowStyle="ToolWindow" Closing="Window_Closing" ResizeMode="NoResize"> <Grid> <!-- テキスト --> <Label x:Name="TextInputLabel" Content="テキスト入力" HorizontalAlignment="Left" Height="18" Margin="21,19,0,0" VerticalAlignment="Top" Width="141" VerticalContentAlignment="Center" Padding="5,1"/> <TextBox x:Name="TextInputText" HorizontalAlignment="Left" Height="20" Margin="186,18,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="176" Text="帳票保存先"/> <!-- フォルダ選択 --> <Label x:Name="FolderPathLabel" Content="フォルダ選択" HorizontalAlignment="Left" Height="18" Margin="21,63,0,0" VerticalAlignment="Top" Width="127" VerticalContentAlignment="Center" Padding="5,1"/> <TextBox x:Name="FolderPathText" HorizontalAlignment="Left" Height="20" Margin="186,62,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="374"/> <Button x:Name="FolderPathButton" Content="…" HorizontalAlignment="Left" Height="19" Margin="565,62,0,0" VerticalAlignment="Top" Width="23" Click="FolderPathButton_Click" RenderTransformOrigin="0.652,-0.632"/> <!-- スピン --> <Label x:Name="SpinLabel" Content="スピン(0~100)" HorizontalAlignment="Left" Height="23" Margin="21,104,0,0" VerticalAlignment="Top" Width="127" Padding="5,1" VerticalContentAlignment="Center"/> <WindowsFormsHost x:Name="SpinText" Grid.Row="0" Margin="186,128,343,151" Height="23" VerticalAlignment="Bottom" > <wfm:NumericUpDown x:Name="Spin1" x:FieldModifier="public" Value="5" Minimum="0" Maximum="100" /> </WindowsFormsHost> <!-- ボタン --> <Button x:Name="ApplyButton" Content="適用" HorizontalAlignment="Left" Height="27" Margin="21,152,0,0" VerticalAlignment="Top" Width="84" Click="ApplyButton_Click"/> <Button x:Name="CloseButton" Content="閉じる" HorizontalAlignment="Left" Height="27" Margin="506,152,0,0" VerticalAlignment="Top" Width="82" Click="CloseButton_Click"/> </Grid> </Window> 設定画面を閉じてもアプリは終了しないよう、画面の表示・非表示を切り替えるような作りにする。 インスタンスは共通でいいので、シングルトンパターンで実装する。 MainWindow.xaml.cs private static MainWindow _Instance; public static MainWindow GetInstance() { if (_Instance == null) { _Instance = new MainWindow(); } return _Instance; } タスクトレイのアイコンクリックで設定画面を表示させるようにする。 App.xaml.cs // アイコンクリック private void NotifyIcon_Click(object sender, System.Windows.Forms.MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Left) { ResidentProgramSample.MainWindow.GetInstance().Show(); } } 以下のように、タスクトレイのアイコンクリックで、設定画面が開かれるようになる。 拡張機能 拡張機能は、最低限はC#だけ追加すればよかったと思います。 (テスト環境でインストールされていた拡張機能は以下の通りです。念のため。。。) 最後に いちおう VSCode でも開発できることは確認できましたが、結局今回はおとなしく Visual Studio を使用することにしました・・・。 理由は、以下の3点です。 1.画面デザイナーの機能がないので、画面デザインの追加・変更があったときに面倒そう。 2.今後、Excel操作などの機能拡張を予定しているが、ライブラリの呼び出し方などを調べるのが面倒そう。 3.実行環境に.Net Core をインストールする必要がある。 逆に言うと、上記のような懸念がなければ、VSCode でも問題なく開発できそうです。 全ソース 今回作成したソースを以下に公開しています。 ご参考までに。 ResidentProgramSample 参考サイト WPFでタスクトレイ常駐アプリを作る .NET 5とVSCodeでデスクトップアプリ(ランチャー)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity コンポーネントを持っているかを調べる

0.0 はじめに ゲームオブジェクトに特定のコンポーネントが含まれているか調べる 1.0 結論 GetComponent<>()の返り値で判断可能です。 例えばゲームオブジェクトにRigidbodyコンポーネントが含まれていればGetComponent< Rigidbody>()の返り値はtrue、含まれてなければ返り値はfalseとなります。、 Test.cs if (GetComponent<Rigidbody2D>()) { Debug.Log("Rigidbody2D含まれてるよ"); } else { Debug.Log("Rigidbody2D含まれてないよ"); gameObject.AddComponent<Rigidbody2D>(); // 含まれてなければ加える } 2.0 おまけ 下記のように[RequireComponent (typeof ())]のアトリビュートを付けているとコンポーネントを自動に追加してくれます。 ポスト typeof ()の()中にコンポーネント名を入れる。 Test.cs [RequireComponent (typeof (Rigidbody))] public class Test : MonoBehaviour { }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#で特定のサイトの情報をスクレイピングしてメールを送るバッチ処理を作ってみました。

はじめに 特定のサイトのお知らせ欄が更新されたかな?と毎回見に行くのが面倒だったのでスクレイピングしてメールを送ってくれるバッチ処理を作ってみました。 色々ライブラリを調べてみるとAngleSharpというライブラリが便利そうだったのでこちらを使って実装してみました。 AngleSharpについての記事は以下がとても分かりやすかったです!ありがとうございます!!! ※メールサーバについてはGmailの無料メールサーバを利用しました。 サンプルコード 今回はサクッと作ったので対象のサイトやメールサーバの情報などはソースコード上にべた書きです。 ちゃんと作る場合はここらへんは外部ファイルにした方が良いと思います。 あとは一つのクラス内にドカッと書いてしまっているのもあれな気がするけどお許しを...。 Program.cs using AngleSharp.Html.Dom; using AngleSharp.Html.Parser; using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace ScrapingApp { class Program { private static readonly HttpClient _client = new(); static async Task Main() { var urlstring = "スクレイピングしたいサイトのURL"; using var stream = await _client.GetStreamAsync(new Uri(urlstring)); var parser = new HtmlParser(); // 指定したサイトのHTMLをストリームで取得する IHtmlDocument doc = await parser.ParseDocumentAsync(stream); // 以下から★部分までは僕の取得したかった情報を取得するためにセットしている // だけなのでここら辺は取得したい内容によって変わるかと思います。 var tdElements = doc.GetElementById("Id名").QuerySelectorAll("セレクタ"); var yearStr = DateTime.Now.Year.ToString(); // メールのBodyに入れる内容をStringBuilderへ入れていきます。 StringBuilder sb = new(); foreach (var tr in tdElements) { // 今回のサイトでは年が表示されていなかったので追加しています。 var dttmText = $"{yearStr}/{tr.QuerySelector("セレクタ").TextContent}"; // 1日前からアプリ実行までの期間内に更新された内容があればメールの本文に追加します。 // 今回はテキストとURL(href)をメールで送ります。 if (DateTime.Now.AddDays(-1).CompareTo(DateTime.Parse(dttmText)) > 0) break; sb.AppendLine($"{tr.QuerySelector("セレクタ").TextContent} 投稿日付:{dttmText}"); sb.AppendLine(tr.QuerySelector("セレクタ").GetAttribute("href")); } // ★ // ここからはMailKitというライブラリを用いてメールを送る処理を行っています。 var message = new MimeKit.MimeMessage(); message.From.Add(new MimeKit.MailboxAddress("メール送信元の名前", "メール送信元のアドレス")); message.To.Add(new MimeKit.MailboxAddress("メール送信先の名前", "メール送信先のアドレス")); message.Subject = "〇〇通知を送ります~~"; var textPart = new MimeKit.TextPart(MimeKit.Text.TextFormat.Plain) { Text = string.IsNullOrEmpty(sb.ToString()) ? "新規〇〇探しにいったけどなかった(;´∀`)\n無駄にメール送ってごめんねm(__)m" : sb.ToString() }; message.Body = textPart; using var client = new MailKit.Net.Smtp.SmtpClient(); try { await client.ConnectAsync("smtp.gmail.com", 587); await client.AuthenticateAsync("Gmailのメールアドレス", "Gmailのアカウントパスワード"); await client.SendAsync(message); await client.DisconnectAsync(true); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } } } と、これをタスクスケジューラにセットして毎日7時と18時に送られてくるのを待つ...と。 おわりに それにしてもこんな簡単にスクレイピングできちゃうなんてAngleSharp良いですね!! もし業務でスクレイピングする機会があればこのライブラリを使ってみよう~~!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Action, Func, Predicateデリゲートを使ってみた!

はじめに Action, Func, Predicateデリゲートについて、使い方をざっくりまとめてみました。 あくまで使い方に全振りしたので正直なところ実用性は皆無なサンプルコードになっています(;´∀`) 使い方を理解する一助になれば幸いです。 Actionデリゲート デリゲート 戻り値 説明 Action<T> void 一つのT型の引数を受け取り、戻り値は返さない処理を行います 今回はAction<T>を利用しましたが引数なしのActionや引数が2つのAtion<T1,T2>等々、最大で引数を16個受け取るActionデリゲートまで存在します。 Funcデリゲート デリゲート 戻り値 説明 Func<T,TResult> TResult 一つのT型の引数を受け取り、TResult型の戻り値を返す処理を行います 今回はFunc<T>を利用しましたが引数なしのFunc<TResult>や引数が2つのFunc<T1,T2,TResult>等々、最大で引数を16個受け取るFuncデリゲートまで存在します。 Predicateデリゲート デリゲート 戻り値 説明 Predicate<T> bool 一つのT型の引数を受け取り、bool型の戻り値を返す処理を行います こちらは...ActionやFuncみたいにたくさんデリゲートがあるわけではなさそうですね。 とりあえずこれだけのようです。 サンプルコード Program.cs //C#9.0以降のトップレベルステートメントを利用しています using System; //"Actionデリゲートを使ってみた。"という文字列をactionデリゲートが受け取り //そのままConsoleに出力してるだけ UseAction("Actionデリゲートを使ってみた。", o => Console.WriteLine(o)); //"Funcデリゲートを使ってみた。"という文字列をfuncデリゲートが受け取り //そのままConsoleに出力してるだけ UseFunc("Funcデリゲートを使ってみた。", o => o); //"Predicateデリゲートを使ってみた。"という文字列をpredicateデリゲートが受け取り //"Predicateデリゲートを使ってみた。"と比較して等値か確認して結果をConsoleに出力してるだけ UsePredicate("Predicateデリゲートを使ってみた。", o => o == "Predicateデリゲートを使ってみた。"); //UseActionメソッドは引数strを受け取り、何も返さない(void)actionデリゲートを実行します。 void UseAction(string str, Action<string> action) => action(str); //UseFuncメソッドは引数strを受け取り、string型の戻り値を返すfuncデリゲートを実行します。 //funcデリゲートはstring型の戻り値を返すのでその値をConsole.WriteLineで出力します。 void UseFunc(string str, Func<string, string> func) => Console.WriteLine(func(str)); //UsePredicateメソッドは引数strを受け取り、bool型の戻り値を返すpredicateデリゲートを実行します。 //predicateデリゲートはbool型の戻り値を返すのでその値をConsole.WriteLineで出力します。 void UsePredicate(string str, Predicate<string> predicate) => Console.WriteLine(predicate(str)); 出力結果 Actionデリゲートを使ってみた。 Funcデリゲートを使ってみた。 True おわりに デリゲートって結構説明が難しいというか、一回見ただけだとなかなかイメージがわかないんですよね...。 ここは使いながら慣れていくしかないのかな(;^_^A
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む