- 投稿日:2020-11-08T22:59:34+09:00
ハッカーっぽいやつ作ってみた ~ コーディングを記録して動画風に再生する
はじめに
いつもの通り作りっぱなしなので、バグってるかもしれません。
デモ用
See the Pen CodingDemo by kob58im (@kob58im) on CodePen.
※デモのサンプルコードがミスっております。1
配列作成用
CodePen上で開いたほうが使いやすいです。(リロードすれば消去できます())
保存機能とかは無いので、ガチなコーディングはちゃんとしたエディタなりでやってくださいまし。
See the Pen CodeRecorder by kob58im (@kob58im) on CodePen.
textarea
に入力された文字列について、更新イベントの前後の差分を強引に抽出して、編集履歴を出力するようにしてます。お試し用
とりあえずC#とJavaScriptを指定してあります。
See the Pen CodePlayer - for - C# by kob58im (@kob58im) on CodePen.
参考 - highlight.js
参考 - 作成時にお世話になったところ
- JavaScriptで正規表現(文字列置換え編) - Qiita
- ブラウザから手軽にチェックできる正規表現テストツールまとめ
- Regulex:JavaScript Regular Expression Visualizer
Main
のところ、static
が抜けている。 ↩
- 投稿日:2020-11-08T22:35:10+09:00
C#で湯婆婆を実装してみる
はじめに
原作者様: Javaで湯婆婆を実装してみる - Qiita
拙作「Python 3で湯婆婆を実装してみる - Qiita」に続いて、調子に乗って2言語目でやってみました。
暇人かよ。コード
C#のコンソールアプリ (.NET Core 3.1) でお願いします。
Yubaba.csusing System; namespace Yubaba { class Yubaba { static void Main(string[] args) { Console.WriteLine("契約書だよ。そこに名前を書きな。"); var name = Console.ReadLine(); Console.WriteLine($"フン。{name}というのかい。贅沢な名だねぇ。"); var random = new Random(); var newNameIndex = random.Next(name.Length); var newName = name[newNameIndex]; Console.WriteLine($"今からお前の名前は{newName}だ。いいかい、{newName}だよ。分かったら返事をするんだ、{newName}!!"); } } }ちなみに
$"..."
の書き方は↓これです。私もさっき知りました。
【C#】string.Format() をやめて $"{}"(文字列補間式)を使う - Qiita実行例
契約書だよ。そこに名前を書きな。 田中二郎 フン。田中二郎というのかい。贅沢な名だねぇ。 今からお前の名前は田だ。いいかい、田だよ。分かったら返事をするんだ、田!!クラッシュ湯婆婆
名前が空だと落ちてしまう湯婆婆。
原作リスペクト。あえて直さない。契約書だよ。そこに名前を書きな。 フン。というのかい。贅沢な名だねぇ。 Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at System.String.get_Chars(Int32 index) at Yubaba.Yubaba.Main(String[] args) in C:\...\Yubaba.cs:line 15「?」田さんが契約したら…
コマンドプロンプト(Visual Studioのデバッグコンソール)が「?」田さんを受け付けてくれない。コピペで入力した時点で化けてる。
?田さんをまともな名前で働かせてあげてください。契約書だよ。そこに名前を書きな。 ��田 フン。??田というのかい。贅沢な名だねぇ。 今からお前の名前は?だ。いいかい、?だよ。分かったら返事をするんだ、?!!関連: 吉田さんは通すけど?田さんは通さないミドルウェア - Qiita
(2020/11/09) ?田さんが働けるようになったようです。
→C# で湯婆婆を実装してみる(?田さんにも対応) - Qiita
- 投稿日:2020-11-08T18:18:41+09:00
Unityの非同期復帰の実装を覗いてみた(Async/Await/Coroutine)
WPFの非同期復帰の実装を覗いてみた(Async/Await/Dispatch.BeginInvoke)
のUnity版です。合わせて読んでいただきたい想定です。Unityのコルーチンシステム
Unityの非同期システムは基本的にはイテレーターオブジェクトを転用したコルーチンです。
StartCoroutine(MethodAsync()); IEnumerator MethodAsync() { // メインスレッド yield return new WaitForSecond(10); m_UIText.color = Color.black; // メインスレッド bool isWaiting = true; int result = 0; // メインスレッド new Thread(() => { // ワーカースレッド for(int i = 0; i < 100000000; i++) result += i; // ハチャメチャ重い処理 isWaiting = false; // ワーカースレッド }).Start() // メインスレッド while(isWaiting) yield return null; // ワーカースレッドの処理が終わるまでフレームを送り続ける m_UIText.text = $"RESULT: {result}"; }WPFでは、ワーカースレッド側のコンテキスト(≒スレッド)で
Dispatch.BeginInvoke
を叩くことで、後続のメインスレッドでやって欲しい処理をキューに積んでいました。Unityでは「終わってるかどうかをメインスレッド側から積極的に確認しに行く」という実装ができます。
そして多分このやり方が一般なんじゃないかなと個人的には思います。言葉を濁しているのは、ゲームというアプリケーションの特性上、ワーカースレッドを使うよりもタイムスライス(複数フレームでちょっとずつ進める)や、そもそも待つほど重い処理をすることが稀で待ちが発生するのはディスクアクセスやネットワークアクセス等標準ライブラリで事足りてワーカースレッドを意識しなくてもよい場合が多いからです。そしてそれら標準ライブラリの中身がどうなっているのかは公開されていません。
その代わりに、WPFの
Dispatcher.BeginInvoke
を使った場合だとどんどんネストが深くなっていくのに対して、コルーチンでは結構フラットに書くことができます。内部は公開されていないのですが、おそらく次のような実装になっているものと思われます。(あくまでイメージです)
static void Main(string[] _) { var coroutinIterators = new List<IEnumerator<YieldInstruction>>(); // 終了していないコルーチンのリスト while(true) // 半無限ループ { var frameTime = StopWatch.Start(); foreach(var coroutine in coroutinIterators.ToArray()) { var completed = !coroutine.MoveNext(); var next = coroutine.Current; if(completed) { coroutinIterators.Remove(coroutine); } else if(next != null) { coroutinIterators.Remove(coroutine); coroutinIterators.Add(WaitInstruction(instruction, coroutine)); } } while(frameTime.Elapsed < TimeSpan.FromSeconds(1.0/60)); // 60FPS Repaint(); } } static IEnumerator WaitInstruction(YieldInstruction instruction, IEnumerator<YieldInstruction> continueCoroutinr) { while(instruction.keepWaiting) yield return null; forech(var item in continueCoroutinr) { yield return item; } }AsyncAwaitの動作の実装
UnityでもC#である以上、awaitすると
SynchronizationContext.Current.Post
によってメインスレッドに復帰するということは代わりません。WPFではSynchronizationContext.Current
にDispatcherSynchronizationContext
というDispatcher.BeginInvoke
に流す実装になっていました。Unityではこれに相当するUnitySynchronizationContext
があります。
https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/UnitySynchronizationContext.cs#L59
m_AsyncWorkerQueue
というのが出てきますが、これは恐らくコルーチンのリストとは別のキューだと思われます。
そしてこのm_AsyncWorkerQueue
の中身を実行しているのがUnitySynchronizationContext.ExecuteTasks
…なのですが、これはネイティブから呼ばれているのでこの先は公になっていないところです。おそらくですが、以下のようなコルーチンがグローバルで走っているイメージなんじゃないかなと思います。StartCoroutine(ExecuteTaskCoroutin()); // エンジンの初期化タイミング(ユーザーコードが走りだす前に) IEnumerator ExecuteTaskCoroutin() { while(true) { UnitySynchronizationContext.ExecuteTasks(); yield return null; } }Coroutine vs AsyncAwait
UnityのC#は周回遅れということもあって、そもそもAsyncAwaitが使えないPJも多々あると思います。
また、長いことコルーチンでやってきた中に現れたAsyncAwaitは眉唾ものとして扱われがちな気がします…
(いやC#に入ったのもう10年前なんですけど…)パフォーマンスの観点からみると、コルーチンはアクティブなコルーチン全てに対して毎フレーム監視をする必要がああります。(さすがにWaitForSecondとかは最適化が入ってそうですが)一方でAwaitでは、毎フレームの監視対象はキュー一つだけで済みます。確かにTaskや取れに類するオブジェクトは軽くはないですが、それはEnumeratorも同じです。故にAsyncAwaitに軍配が上がるケースが大半に思われます。
表現力の観点からみると、コルーチンにはいいところが一つもありません。Awaitでは「戻り値が返せる」「例外を伝播できる」「ラムダ式で書ける」「Task関連の便利ライブラリが使える」などありますが、コルーチンでは一切できません。強いて言うなら普通の配列から
GetEterator
でコルーチンをつくることもできますが、使い道はないでしょう。読みやすさの観点からみると、これは主観に依るところが大きいので何とも言えませんが…。Coroutineではメソッド中でコンテキストが変わってしまう可能性がありませんが、Asyncの場合それがありえます。
ConfigureAwait(false)
は完了後の呼び出し元コンテキストへの復帰を無効にします。こういったけ―スは多くはないでしょうけど。まとめというか余談
実際に中身を覗いてみると、特性がよくわかりますね。
私は今、幸いにもAwait書いてもいいよーな環境にしばらく居るのですが、頑張ってコルーチンこねこねしているところを直すときなどに何か言われそうな気がしてしまします。そういったときにちゃんと説明できないと「やっぱりレガシーの方が良いんじゃないの?」ってなっちゃいますし、コードレビューをする際にも自信をもって「Awaitですっきり書けないでしょうか?」と提案ができるものです。
そんなことを思って書いた二本でした。
- 投稿日:2020-11-08T15:11:17+09:00
[WPF]MVVMパターンでViewModelからViewへのリクエスト
MVVMパターンでWPFをコーディングしている時、どうしても処理の途中でオペレータの判断を仰いだりしたいこともあるし、
ViewModel内のコマンドから画面を出したいor閉じたい要望が出てくる。コードビハインドを増やしたくはなく、当然ViewModelから参照するわけにはいかない時、"メッセンジャー"という仕組みを用いる
このメッセンジャーという仕組みを使って以下の3つを実現してみる
- ViewModelから内容を指定したメッセージボックスの表示
- ViewModel・Modelから進捗表示を更新できるプログレスバーの表示
- ウィンドウのClose
いろいろと調べてみるとPrismライブラリというものを使えば楽に実装できるようだが、そこは天邪鬼なので使わない。(おい)
ただし、Viewプロジェクトに対してNuGetで下記ライブラリをインストールしている。
- System.Windows.Interactivity.dll
- Microsoft.Expression.Interactions.dll
今回作ったもの
Viewの実装
まずMainViewから。ポイントはSystem.Windows.Interactivityインストールして使用可能になった下記xmlns
xmlns:iy="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:is="http://schemas.microsoft.com/expression/2010/interactions"これを使用してViewModel側の特定のプロパティが発火した時に紐づけたクラス・メソッドを呼び出せるようにしている。
ボタンとラベルについては特に説明することは無い。MainView.xaml<Window x:Class="WpfTestView.MainView" 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:iy="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:is="http://schemas.microsoft.com/expression/2010/interactions" xmlns:local="clr-namespace:WpfTestView" xmlns:vm="clr-namespace:WpfTestViewModel;assembly=WpfTestViewModel" mc:Ignorable="d" Title="MainWindow" Height="200" Width="200"> <Window.DataContext> <vm:MainViewModel/> </Window.DataContext> <!-- TriggerにバインドされているのがViewModel側のプロパティ名。中身はイベントキャッチ時の被実行クラス --> <iy:Interaction.Triggers> <is:PropertyChangedTrigger Binding="{Binding ShowMessageBoxRequest}"> <local:ShowMessageBox/> </is:PropertyChangedTrigger> <is:PropertyChangedTrigger Binding="{Binding ShowProgressBarRequest}"> <local:ShowProgressBar/> </is:PropertyChangedTrigger> </iy:Interaction.Triggers> <Grid> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" > <Button Content="Execute" Command="{Binding Run}" Height="30" MinWidth="80" Margin="0,0,0,10" /> <Label Content="{Binding ResultString}" Margin="0,10,0,0" HorizontalContentAlignment="Center" /> </StackPanel> </Grid> </Window>次にProgressBar用のViewを見てみる。
ProgressView.xaml<Window x:Class="WpfTestView.ProgressView" 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:iy="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:is="http://schemas.microsoft.com/expression/2010/interactions" xmlns:local="clr-namespace:WpfTestView" mc:Ignorable="d" WindowStyle="None" WindowStartupLocation="CenterOwner" Title="ProgressView" Height="50" Width="250" > <iy:Interaction.Triggers> <is:PropertyChangedTrigger Binding="{Binding CloseWindowRequest}"> <local:CloseWindow/> </is:PropertyChangedTrigger> </iy:Interaction.Triggers> <Grid> <ProgressBar Value="{Binding ProgressValue}" Minimum="0" Maximum="100" Margin="2" VerticalAlignment="Stretch" /> </Grid> </Window>MainView.xamlと同様にイベントを紐づけている。こちらについてはウィンドウを閉じる為の処理。
イベントキャッチ時に実行される肝心の処理については以下の通り。InteractionRequestReceiver.cspublic class ShowMessageBox : TriggerAction<FrameworkElement> { protected override void Invoke(object parameter) { if (parameter is DependencyPropertyChangedEventArgs e && e.NewValue is ShowMessageBoxRequest msgReq) { msgReq.Result = MessageBox.Show(msgReq.Text, msgReq.Title, msgReq.Button, msgReq.Icon, msgReq.DefaultResult, msgReq.Options); } } } public class ShowProgressBar : TriggerAction<FrameworkElement> { protected override void Invoke(object parameter) { if (parameter is DependencyPropertyChangedEventArgs e && e.NewValue is ShowProgressBarRequest progBarReq) { new ProgressView() { DataContext = progBarReq.ProgressViewModel, Owner = Window.GetWindow(AssociatedObject) }.ShowDialog(); } } } public class CloseWindow : TriggerAction<FrameworkElement> { protected override void Invoke(object parameter) { if (parameter is DependencyPropertyChangedEventArgs e && e.NewValue is CloseWindowRequest) { Window.GetWindow(AssociatedObject).Close(); } } }TriggerAction<FrameworkElement>を継承することでイベントの発火をトリガにInvokeメソッドの実行に入るようになっている。
プロパティ変更イベントの為、引数として渡されているparameterにはDependencyPropertyChangedEventArgsが込められている。
そして、変更後プロパティの値をキャストすることでその中身を取り出すという流れ。ViewModelの実装
まず最初にViewModelが2つあるので、共通のViewModelBaseを書いておく
ViewModelBasepublic abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { if (PropertyChanged == null) return; PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private ShowMessageBoxRequest _showMessageBoxRequest = null; public ShowMessageBoxRequest ShowMessageBoxRequest { get => _showMessageBoxRequest; set { _showMessageBoxRequest = value; if (value != null) OnPropertyChanged(); } } private ShowProgressBarRequest _showProgressBarRequest = null; public ShowProgressBarRequest ShowProgressBarRequest { get => _showProgressBarRequest; set { _showProgressBarRequest = value; if (value != null) OnPropertyChanged(); } } private CloseWindowRequest _closeWindowRequest = null; public CloseWindowRequest CloseWindowRequest { get => _closeWindowRequest; set { _closeWindowRequest = value; if (value != null) OnPropertyChanged(); } } }OnPropertyChangedはよくある実装。それ以外のプロパティに関しては上記View側でも見た通り。
その中身については簡単なメンバのみ持つ(あるいは何も持たない)クラスInteractionRequest.cspublic class ShowMessageBoxRequest { public string Text { get; set; } public string Title { get; set; } = ""; public MessageBoxButton Button { get; set; } = MessageBoxButton.OK; public MessageBoxImage Icon { get; set; } = MessageBoxImage.Information; public MessageBoxResult DefaultResult { get; set; } = MessageBoxResult.Cancel; public MessageBoxOptions Options { get; set; } = MessageBoxOptions.None; public MessageBoxResult Result { get; set; } = MessageBoxResult.Cancel; } public class ShowProgressBarRequest { public ProgressViewModel ProgressViewModel { get; set; } } public class CloseWindowRequest { }
MessageBoxRequest
メッセージボックスに表示する内容やキャプション、アイコンなど定義できるプロパティクラス
ShowProgressBarRequest
プログレスバーを表示する為のViewModelのみ持つクラス
CloseWindowRequest
ウィンドウ非表示リクエスト用のクラス
これらInteractionRequest.csで定義された各クラスが、先述のInteractionRequestReceiver.csで参照されている。
さて、いよいよViewModelを見てみる
MainViewModel.cspublic class MainViewModel : ViewModelBase { private string _resultString = "Default"; public string ResultString { get => _resultString; set { _resultString = value; OnPropertyChanged(); } } private ICommand _run = null; public ICommand Run => _run ?? (_run = new RelayCommand(RunCommandExecute)); private void RunCommandExecute(object parameter) { ShowMessageBoxRequest = new ShowMessageBoxRequest() { Text = "Hello World!", Title = "Caption", Button = MessageBoxButton.YesNoCancel, Icon = MessageBoxImage.Question, DefaultResult = MessageBoxResult.Cancel }; ResultString = ShowMessageBoxRequest.Result.ToString(); if (ShowMessageBoxRequest.Result == MessageBoxResult.Yes) { ShowProgressBarRequest = new ShowProgressBarRequest() { ProgressViewModel = new ProgressViewModel(new SampleSequencer()) }; } } }ボタンからのコマンドにバインドしているRelayCommandはICommandを継承したよくある実装なので割愛。
このコマンドでは上記〇〇Requestクラスをインスタンス化し、ViewModelBaseに記載されているプロパティに代入している。
プロパティへの代入(Setterへのアクセス)時にPropertyChangedイベントが発火し、View側の処理が始まるという仕組み。MessageBox.Showや、Window.ShowDialogは子ウィンドウの終了を同期待ちする為、閉じるまでこのコマンド内部の処理は止まることになる。
次に、ProgressViewModelを見ていくが、MainViewModel内でProgressViewModelに渡しているSampleSequencerについてはModel部のクラスなので後述ProgressViewModelpublic class ProgressViewModel : ViewModelBase { private MonitorableSequencer Sequencer { get; set; } public ProgressViewModel(MonitorableSequencer sequencer, object parameter = null) { Sequencer = sequencer; Sequencer.Update += (o, e) => { if (o == null || !int.TryParse(o.ToString(), out int value)) return; if (value < 0 || 100 < value) return; if (Application.Current.Dispatcher.CheckAccess()) { ProgressValue = value; } else { Application.Current.Dispatcher.BeginInvoke((Action)(() => { ProgressValue = value; })); } }; Execute(parameter); } private int _progressValue = 0; public int ProgressValue { get => _progressValue; set { _progressValue = value; OnPropertyChanged(); } } private void Execute(object parameter) { Task.Factory.StartNew(() => { Sequencer.SequenceStart(parameter); CloseWindowRequest = new CloseWindowRequest(); }); } }ProgressBarを表示する部分に関してはコンストラクタの最後にExecuteメソッドを呼び、その中でタスクを開始しているだけ。
コンストラクタの中でいろいろ書かれているのはModel側でシーケンス進行時に進捗更新イベントを受け取る為の初期化。必要なシーケンスを実行したのち、CloseWindowRequestのプロパティを更新することでウィンドウを閉じる処理を開始させている
Modelの実装
最後に、Model側の実装。ViewModel側でいろいろ書かれていたが、Model側の実装は少ない
SampleSequencer.cspublic abstract class MonitorableSequencer { public event EventHandler<EventArgs> Update; public abstract bool SequenceStart(object parameter); public void UpdateProgress(int value) => Update?.Invoke(value, null); } public class SampleSequencer : MonitorableSequencer { public override bool SequenceStart(object parameter) { for (int i = 0; i < 10; i++) { UpdateProgress(i * 10); System.Threading.Thread.Sleep(500); } return true; } }わざわざ抽象クラスを作る必要もなかったかもしれないが、一応そこは汎用性の為作ってみた。
これによりMainViewModel側でProgressViewMdoelインスタンス化の際に、MonitorableSequencerクラスを継承した複数のSequencerを別個に定義することができ、
それら各Sequencerの中身でUpdateProgressを呼び出すことでProgressBarの表示が更新されるという仕組み。ちなみに、ProgressValueについては0~100固定にしていて、ProgressViewModel側で値のチェックはしているものの、
進捗度更新の際に0~100までの値を注意して入れなければならないのは少し気になる。もう少しいい方法があるかもしれない。
(少なくともMin/Maxは定義時に動的に変えてもいいかもしれない)長ったらしく書いたが、自分なりに嬉しいのは*.xaml.csに全く手を加えていないこと。
View->ViewModel->Modelの依存関係は全く崩さずにViewModel->Viewへの流れが作れるのはとても嬉しい
- 投稿日:2020-11-08T13:52:49+09:00
[.NET] Fluent Assertionsを使って、きれいで読みやすいテストコードを書く
はじめに
Fluent Assertions は、.NETのテストコードを流麗で読みやすくしてくれるライブラリです。
日本語解説記事があまり見当たらないので、ここでまとめてみようと思う次第です。JavaScriptのJasmineを知っているかたでしたら、jasminのmatchersの.NET版みたいなものと思ってください。
Fluent Assertions使うとどうなるの?
だいたいのテストランナーでは、このようなテストコードを書きます。
Assert.Equal(期待値, 実際の値);Fluent Assertionsを使うと、このように書きます。
実際の値.Should().Be(期待値);「プログラミングは文学である」と言われます。
どうでしょ?文学ぽいですか?導入
NuGet から導入できます。
Visual Studioの場合は、プロジェクトを右クリックすると現れるメニューからインストールできます。
いろいろなアサーションの書き方
よく使うものをあげてみます。
// 値が等しい 実際の値.Sholud().Be(期待値); // 値がNULL 実際の値.Sholud().BeNull(); // 値がNULLではない 実際の値.Sholud().NotBeNull(); // 値がTrue 実際の値.Sholud().BeTrue(); // 値がFalse 実際の値.Sholud().BeFalse(); // 値が同じオブジェクト 実際の値.Should().BeSameAs(期待値);コレクション関係
コレクション関係でよく使うものです。
// 同じ要素を含む 実際の値.Should().BeEquivalentTo(期待値); // 空のコレクション 実際の値.Should().BeEmpty();日時関係
日時関係でよく使うものです。
// 期待値より後 実際の値.Should().BeAfter(期待値); // 期待値より前 実際の値.Should().BeBefore(期待値); // 期待値とズレ(単位:ms)の範囲内 実際の値.Should().BeCloseTo(期待値, ズレ);
- 投稿日:2020-11-08T13:33:33+09:00
自作ソフトに.chmヘルプファイルをつける
業務用ソフトを作成する機会があり, ヘルプファイルを含めて配布しました.
その際, 複数個所躓いたので備忘録を兼ねて纏めます.目次
- 環境
- ヘルプファイルを作る
- 文書作成
- コンパイル
- プログラムからヘルプを表示する
- 最後に
環境
作成したソフト : Outlook VSTOアドイン .NET Framework 4.7.2
開発環境 : Visual Studio 2019
開発言語 : C#ヘルプファイルを作る
ヘルプファイルは,
- リッチテキストで文書を作成する.
- 文書をコンパイラでヘルプファイルに変換する.
という2ステップで作成できます.
今回は, メンテナンス性からWord文書をdoc2htmlhelpというフリーソフトでコンパイルする方式を採用しました.
文書作成
Wordで作成したとおりに表示されるので, デザイン面で気にする所はありません.
ただ, ヘルプファイルの目次はWordに目次を挿入していないと作られないので, 注意してください.コンパイル
doc2htmlhelpとHTML Help Workshopをダウンロードしましょう.
以下にダウンロードリンクを示します. (2020/11/07)
doc2htmlhelp
HTML Help Workshopコンパイルは簡単で, doc2htmlhelp.vbsを起動したら出てくるファイルダイアログでWord文書を選択するだけです.
コンパイルすると, 以下の様なファイルが生成されます.
プログラムからヘルプを表示する
標準で用意されている関数を使いました.
トップページを表示する場合は, 第2引数にchmヘルプファイルへのパスを指定します.
第1引数は, 親要素となるコントロールですがなくてもよさそうです.public static void show(){ System.Windows.Forms.Help.ShowHelp(null, @"c:/software/help/manual.chm"); }指定した章を開く場合は以下の通りです.
public static void show(){ // 2章を開く System.Windows.Forms.Help.ShowHelp(null, @"c:/software/help/manual.chm", @"c:/software/help/doc_2.htm"); }第3引数に, htmファイルで章を指定します.
しかし, doc2htmlhelpで作成した場合, ファイル名がdoc_0.htm, doc_1.htmのようなただの連番になってしまい, 指定しづらくなります. 手動でファイル名を変更しても良いですが, スマートでないので自分は生成されるhhcファイルを読むことにしました.hhcファイルはヘルプの目次部分の生成に使用されます. 内容は以下のようになっています.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <HTML> <HEAD> </HEAD><BODY> <OBJECT type="text/site properties"> <param name="ImageType" value="Folder"> </OBJECT> <UL> <LI><OBJECT type="text/sitemap"> <param name="Name" value="manual"> <param name="Local" value="doc_0.htm"> </OBJECT> <LI><OBJECT type="text/sitemap"> <param name="Name" value="見出し1"> <param name="Local" value="doc_1.htm#_Toc55685037"> <param name="ImageNumber" value="1"> </OBJECT> <UL> <LI><OBJECT type="text/sitemap"> <param name="Name" value="見出し2"> <param name="Local" value="doc_1.htm#_Toc55685038"> </OBJECT> </UL> </UL> </BODY></HTML>見てみると, name属性が
Name
,Local
である2つのparamタグを使えば見出し名に対応したファイル名が取得できそうです.
そこで, 引数に見出し名を与えることで対応したヘルプを開ける関数を書きました.
以下がそのコードです.using HtmlAgilityPack; public static void Show(string index) { string contents = File.ReadAllText(@"C:\software\help\manual.hhc", encoding: System.Text.Encoding.GetEncoding("shift_jis")); var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(contents); HtmlNode node = htmlDoc.DocumentNode.SelectSingleNode($@"//param[@name=""Name"" and @value=""{index}""]/following-sibling::param[@name=""Local""]"); if (node == null) return; System.Windows.Forms.Help.ShowHelp(null, @"C:/software/help/manual.chm", node.Attributes["value"].Value); }タグの取得にHTML解析ライブラリのHtml Agility Packを使用しています.
最後に
ヘルプファイルの作成から表示まで粗末ですが纏めました.
もし, 記事に間違っているところや改善点あればご助言お願いします.参考サイト
chmファイル関係
Mr.Big~小技集・自作ヘルプの利用
Word から 簡単にオンラインヘルプファイル(.chm)を生成できるソフト「doc2htmlhelp」が便利C#関係
- 投稿日:2020-11-08T11:57:26+09:00
[WPF]TreeViewにXElementを属性付きBinding
C#初学者用記事をいろいろ書き込んでる最中ですが、自分の為の備忘録。
WPFのTreeViewにXMLデータをそのまま流し込む方法として、XDocumentを適用する方法はいくつか見つかったものの、XElementを元データとしてBindingする方法がなかなか見つからなかったのでメモ
# そんなニッチなことするやつあんまいないのか
- XDocumentを元データする参考記事
上記参考記事と同様に、気象庁のサンプルデータを使わせてもらうことにする
気象庁防災情報XMLフォーマット 技術資料Viewの実装
基本的には上記参考ページと同じ内容となるので、諸々割愛しつつ、ソースコードを貼る
xamlは以下の通り。
※注意 デフォルトの"MainWindow"という名前から"MainView"という名前に変えているMainView.xaml<Window x:Class="WpfTestView.MainView" 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:WpfTestView" xmlns:vm="clr-namespace:WpfTestViewModel;assembly=WpfTestViewModel" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <vm:MainViewModel/> </Window.DataContext> <Window.Resources> <local:XAttributeConverter x:Key="XAttributeConverter" /> </Window.Resources> <Grid> <Border Margin="10" BorderBrush="Black" BorderThickness="1"> <!-- TreeViewのItemsSourceはIEnumerableでないといけないので、IEnumerable<XElement>--> <TreeView ItemsSource="{Binding XTreeRoot, Mode=OneWay}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Elements}"> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="TagName" Text="{Binding Name.LocalName}" /> <TextBlock x:Name="AttrStart" Text="(" /> <!-- XAttributeはBindingサポートしてないのでConverterを使う --> <ItemsControl ItemsSource="{Binding Converter={StaticResource XAttributeConverter}}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="2,0"> <TextBlock Text="{Binding Name.LocalName}" /> <TextBlock Text="="" /> <TextBlock Text="{Binding Value}" /> <TextBlock Text=""" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> <TextBlock x:Name="AttrEnd" Text=")" /> <TextBlock x:Name="Separater" Text=" : " /> <TextBlock x:Name="TagValue" Text="{Binding Value}" /> </StackPanel> <!-- ノードによって表示形式を切替え --> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding NodeType}" Value="Text"> <Setter TargetName="TagName" Property="Text" Value="Value" /> <Setter TargetName="AttrStart" Property="Text" Value="" /> <Setter TargetName="AttrEnd" Property="Text" Value="" /> <Setter TargetName="Separater" Property="Text" Value="" /> <Setter TargetName="TagValue" Property="Text" Value="" /> </DataTrigger> <DataTrigger Binding="{Binding HasAttributes}" Value="False"> <Setter TargetName="AttrStart" Property="Text" Value="" /> <Setter TargetName="AttrEnd" Property="Text" Value="" /> </DataTrigger> <DataTrigger Binding="{Binding HasElements}" Value="True"> <Setter TargetName="Separater" Property="Text" Value="" /> <Setter TargetName="TagValue" Property="Text" Value="" /> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Border> </Grid> </Window>主に説明しておくべきところとしてはコメントにも書いてるが、XAMLに記載している"XTreeRoot"というプロパティのみがViewModel側とバインドされるもので、それより下階層のBinding要素はXElementオブジェクトにバインドされている
HierarchicalDataTemplate下のStackPanelやTriggersに関しては、完全に個人的な趣向で書いているので自由にカスタマイズ可能ViewModelの前にXAttributeをバインドする為のConverterを書いておく
XAttributeConverter.cspublic class XAttributeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!(value is XElement x)) return Enumerable.Empty<XAttribute>(); return x.Attributes(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }ConvertではXElementオブジェクトのIEnumerableを返すように書いている。
今回の例ではXMLの内容は更新しないのでTreeViewはOneWay(Source→Viewの方向のみ)としている
なのでConvertBackは使用しないのでNotImplementedExceptionとしておく。ViewModelの実装
MainViewModel.cspublic class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { if (PropertyChanged == null) return; PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public MainViewModel() { XDocument xDoc = XDocument.Load(@".\15_12_01_161130_VPWW54.xml"); XTreeRoot = new List<XElement>() { xDoc.Root }; } private IEnumerable<XElement> _xTreeRoot = null; public IEnumerable<XElement> XTreeRoot { get => _xTreeRoot; set { _xTreeRoot = value; OnPropertyChanged(); } } }デフォルトではツリーは全て閉じた状態で始まる為、もし最初から開いた状態にしておきたい場合、
最初に挙げた参考記事を参照方読み込んでいるファイルは適当に選んでいる(大きすぎず、小さすぎないくらいのファイル)
XAMLのコメントにも書いたが、以下2点が肝要。(自分が詰まったので)
* バインドするのはIEnumerable
* XAttributeはバインドサポート外なのでconverter使う
- 投稿日:2020-11-08T11:55:07+09:00
UWPでTabPageを表示する
UWPのTabViewコントロールの使い方
サンプルコード
FormsではTabControl/TabPage、UWPではTabViewになっている。参照追加(NuGet)
最初に、TabViewを使用できるようにパッケージ追加する。
(NuGet パッケージ名:Microsoft.UI.Xamlで検索)
次に、追加したプロジェクトのApp.xamlに下記を追加
App.xaml<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>TabView コード
MainPage.xaml<muxc:TabView x:Name="ContentTabView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> </muxc:TabView>・タブ追加
MainPage.xaml<muxc:TabView x:Name="ContentTabView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <muxc:TabView.TabItems> <muxc:TabViewItem Content="Hello TabView !!" Header="TabPage Xaml" /> </muxc:TabView.TabItems> </muxc:TabView>タブが表示される。
※TabViewはデフォルトでタイトルバーに表示されるが、別途設定が必要こちら
タブの×ボタンでタブを閉じたり、+ボタンでタブを追加できる。
まだイベントを実装していないので、この時点では押しても特に何も起こらない。
タブの×ボタンを非表示にする場合はTabViewItemのIsClosableをfalseに設定する。MainPage.xaml<muxc:TabViewItem Content="Hello TabView!!" Header="TabPage Xaml" IsClosable="false"/>タブの+ボタンを非表示にする場会は、TabViewのIsAddTabButtonVisibleをfalseに設定する。
MainPage.xaml<muxc:TabView x:Name="ContentTabView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsAddTabButtonVisible="false">・タブ追加(コードビハインド)
GridのLoadedイベントハンドラーに追加
MainPage.xaml.csprivate void Grid_Loaded(object sender, RoutedEventArgs e) { var item = new TabViewItem() { Header = "TabPage Code", Content = "Hello TabView!!" }; ContentTabView.TabItems.Add(item); }ここまではタブを選択したり、+ボタンを押せない。
TabViewはタイトルバー上に表示されるので、別途タイトルバーを設定し、各コントロールがマウスフォーカスを受け取れるようにする必要がある。
そこで、TabViewにフッターを作成しタイトルバーに設定する。・フッターを作成
MainPage.xaml<muxc:TabView.TabStripFooter> <StackPanel x:Name="TitleBar" Background="Transparent" Orientation="Horizontal"> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="TabView Fotter" /> </StackPanel> </muxc:TabView.TabStripFooter>作成したフッターをタイトルバーに設定する。
MainPage.xaml.csWindow.Current.SetTitleBar(TitleBar);・DataTemplate
TabViewはListViewと同じようにデータテンプレートを使用できる。
バインドするクラスを作成
TabItemInfo.cspublic class TabItemInfo { public string Hedder { get; set; } public Frame ContentPage { get; set; } public TabItemInfo(){ } }下記でデータテンプレートを設定する。
MainPage.xaml<muxc:TabView x:Name="ContentTabView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TabItemsSource="【バインドするItemsSourceプロパティ名】"> <muxc:TabView.TabItemTemplate> <DataTemplate x:DataType="local:TabItemInfo"> <muxc:TabViewItem Content="{x:Bind ContentPage, Mode=OneWay}" Header="{x:Bind Hedder, Mode=OneWay}" /> </DataTemplate> </muxc:TabView.TabItemTemplate> </muxc:TabView>・イベント追加
タブのボタンイベントやドラッグイベントを追加する。
タブの追加ボタン(+ボタン)押下イベント
MainPage.xaml<muxc:TabView ... AddTabButtonClick="ContentTabView_AddTabButtonClick"> </muxc:TabView>タブの閉じるボタン(×ボタン)押下イベント
MainPage.xaml<muxc:TabView ... TabCloseRequested="ContentTabView_TabCloseRequested" </muxc:TabView>ドラッグイベント
下記のプロパティにtrueを設定することで、ドラッグ操作でタブを並び替えることができる。
なお、独自にDataTemplateを実装している場合、ドラッグで並び替えようとするとエラーが発生する。MainPage.xaml<muxc:TabView ... CanDragTabs="true" CanReorderTabs="true" </muxc:TabView>タブをTabViewの外にドロップした場合
タブをドロップした場所で新しいウィンドウを開くなどしたいときに便利。
MainPage.xaml<muxc:TabView ... TabDroppedOutside="ContentTabView_TabDroppedOutside" </muxc:TabView>
- 投稿日:2020-11-08T09:33:23+09:00
【C#】例外の発生箇所を特定できないNGパターン
はじめに
この記事では例外の発生箇所が特定できないパターンをサンプルコードを用いて紹介し、それに対する解決策をまとめます。
サンプルコード
以下の要件を満たすアプリケーションを考えます。
- ユーザのIDを指定するとユーザ名を出力する
- 例外が発生した場合、ユーザにはシステムエラーが起きた旨を伝える
- 例外の詳細はログに出力し、例外が発生した箇所を特定できるようにする
この要件を満たすように以下のように実装したとしましょう。
実装したコード
Program.csclass Program { static void Main(string[] args) { var svc = new UserService(); try { var userId = int.Parse(args[0]); var userName = svc.GetUserName(userId); Console.WriteLine($"ユーザ名:{userName}"); } catch (ServiceException ex) { // ユーザにはシステムエラーが発生した旨だけを伝える Console.WriteLine("システムエラーが発生しました。"); // 例外の詳細はログに出力する Logger.Write(ex.ToString()); } } }UserService.cspublic class UserService { public string GetUserName(int userId) { var dataAccessor = new UserDataAccessor(); try { var user = dataAccessor.Find(userId); return user.Name; } catch (Exception ex) { throw new ServiceException($"ユーザ名の取得に失敗しました。 ユーザID:{userId}"); } } }UserDataAccessor.cspublic class UserDataAccessor { public User Find(int userId) { // データベースのユーザテーブルから指定されたユーザを返す // ユーザが見つからない場合はnullを返す List<User> users = UsersTable.Load(); return users.FirstOrDefault(u => u.Id == userId); } }実行結果
このコードを実行すると以下のような結果になりました。
ユーザが存在する場合
$ dotnet Sample.dll 1 ユーザ名:田中 太郎ユーザが存在しない場合
$ dotnet Sample.dll 2 システムエラーが発生しました。ログSample.Service.ServiceException: ユーザ名の取得に失敗しました。 ユーザID:2 at Sample.Service.UserService.GetUserName(Int32 userId) in /Projects/Sample/Service/UserService.cs:line 19 at Sample.Program.Main(String[] args) in /Projects/Sample/Sample/Program.cs:line 14例外の発生箇所を特定できない
出力結果を見ると、要件は満たせているように見えますが、実はこのコードは例外の発生箇所を特定できるようにするという要件を満たせていません。
ログの行番号から発生箇所を特定しようとすると、本来の例外の発生箇所ではない行に行き着いてしまいます。
ログat Sample.Service.UserService.GetUserName(Int32 userId) in /Projects/Sample/Service/UserService.cs:line 19UserService.cspublic class UserService { public string GetUserName(int userId) { var dataAccessor = new UserDataAccessor(); try { var user = dataAccessor.Find(userId); // 存在しないユーザの場合はuserがnullになる return user.Name; // ここで例外が発生する。本来はここの行番号をログに書きたい } catch (Exception ex) { throw new ServiceException($"ユーザ名の取得に失敗しました。 ユーザID:{userId}"); // だが、ここの行番号がログに出力される } } }このように新しくnewした例外クラスをスローしてしまうと、本来の例外を上書きし、スタックトレースを消してしまうため、例外の発生箇所が特定できなくなってしまいます。
解決策
この問題を解決するには、例外クラスをnewする際に本来の例外を
InnerException
プロパティに持たせるようにします。
自作した例外の場合は下記のように、InnerException
に入れておくためのコンストラクタを作成します。UserService.cspublic class UserService { public string GetUserName(int userId) { var dataAccessor = new UserDataAccessor(); try { var user = dataAccessor.Find(userId); return user.Name; } catch(Exception ex) { // InnerExceptionに実際に起きた例外を入れてあげる throw new ServiceException($"ユーザ名の取得に失敗しました。 ユーザID:{userId}", ex); } } }ServiceException.cspublic class ServiceException : Exception { // 自作した例外の場合は発生した例外を InnerException に入れておくためのコンストラクタを作成する public ServiceException(string message, Exception inner) : base(message, inner) { } }この状態でログを出力すると、下記のようなログを出力するようになり、ログの行番号から発生箇所を特定することができるようになります。
ログSample.Service.ServiceException: ユーザ名の取得に失敗しました。 ユーザID:2 ---> System.NullReferenceException: Object reference not set to an instance of an object. at Sample.Service.UserService.GetUserName(Int32 userId) in /Projects/Sample/Service/UserService.cs:line 14 --- End of inner exception stack trace --- at Sample.Service.UserService.GetUserName(Int32 userId) in /Projects/Sample/Service/UserService.cs:line 19 at Sample.Program.Main(String[] args) in /Projects/Sample/Sample/Program.cs:line 14まとめ
例外の発生箇所を特定できないパターンとその解決策を紹介しました。
例外処理では本来の例外を消してしまうことがないように十分注意して処理するようにしましょう。それではまた。
TomoProg
- 投稿日:2020-11-08T08:31:33+09:00
[.NET][C#]NUnitのAttribute_Setup,TearDown系を試す
はじめに
今回はNUnitのAttributeで最も重要な
テストメソッド、テストクラスに付けるAttributeを調べてみた。▼NUnit関連記事
[.NET][C#]NUnitを使用して単体テスト自動化 基本編
[.NET][C#]NUnitを使用した単体テストのカバレッジレポートを自動生成する
[.NET][C#]NUnitのClassic Modelアサーションメソッド一覧 ※Assertクラス
[.NET][C#]NUnitのStringAssertアサーションメソッド一覧
[.NET][C#]NUnitのCollectionAssertアサーションメソッド一覧
[.NET][C#]NUnitのFileAssert・DirectoryAssertアサーションメソッド一覧
[.NET][C#]NUnitのAttribute_テストメソッド、テストクラス系を試す実施環境
.NET:3.1.401
C#:8.0
NUnit:3.12.0NUnit Attribute
ソースコードがテスト用のものであることを実行環境に伝えるマーキングで
テスト用のクラスやメソッドに付ける。
また、テスト用のパラメータの指定や事前・事後処理などの実行順序の定義するものなど、様々なAttributeがある。今回はテストの実施前後に処理を行うための
Setup,TearDown系Attributeを試す。1. Setup、TearDown
テストメソッドの前後の共通処理を定義したい場合に使用するAttribute。
[Setup]
を付けたメソッドは各テストメソッド実行前に、
[TearDown]
を付けたメソッドは各テストメソッド実行後に
それぞれ実行される。Setup、TearDownを付けたメソッドはテストメソッドの実行の度に前後で実行される。
そのため、各テストの共通する処理等を実装するのがいいだろう。Setup、TearDown実行例public class SetupTearDownAttributesTest { [SetUp] public void Init() { TestContext.WriteLine("SetUp処理");} [TearDown] public void CleanUp() { TestContext.WriteLine("TearDown処理"); } [TestCase] public void TestA() { TestContext.WriteLine("A method."); Assert.Pass(); } [TestCase] public void TestB() { TestContext.WriteLine("B method."); Assert.Pass(); } } public class NonSetupTearDownAttributesTest { [TestCase] public void TestC() { TestContext.WriteLine("C method."); Assert.Pass(); } }実行結果√ TestA() [1ms] 標準出力メッセージ: SetUp処理 A method. TearDown処理 √ TestB() [< 1ms] 標準出力メッセージ: SetUp処理 B method. TearDown処理 √ TestC() [16ms] 標準出力メッセージ: C method.テストメソッドの単位でSetup、TearDownの処理が実行されているのが分かる。
当然だが、Setup、TearDownのスコープは、同一クラス内のテストメソッドとなる。
上記例の場合、TestCメソッドは別クラスのテストメソッドのため、Setup、TearDown処理は実行されない。Setup、TearDownメソッドがあるクラスを継承した場合、
サブクラスのテストメソッドにもSetup、TearDownの実行が適用される。サブクラスでSetup、TearDown実行例[Category("SetupTearDownAttributesTest")] public class SetupTearDownAttributesSubTest : SetupTearDownAttributesTest { [TestCase] public void TestD() { Console.WriteLine("SubClass D method."); Assert.Pass(); } }実行結果√ TestD() [< 1ms] 標準出力メッセージ: SetUp処理 SubClass D method. TearDown処理なお、 SetUpメソッドで例外がスローされた場合、テストメソッド、およびTearDownメソッドは実行されない。
2. OneTimeSetUp、OneTimeTearDown
テストクラスの前後の共通処理を定義したい場合に使用するAttribute。
[OneTimeSetUp]
を付けたメソッドはクラスのテストメソッド実行前に、
[OneTimeTearDown]
を付けたメソッドはクラスのテストメソッド実行後に
それぞれ1回だけ実行される。OneTimeSetUp、OneTimeTearDown実行例public class OneTimeSetupTearDownAttributesTest { private string setupStr; private string teardownStr; [OneTimeSetUp] public void Init() { setupStr = "OneTimeSetUp処理";} [OneTimeTearDown] public void CleanUp() { teardownStr = "OneTimeTearDown処理"; } [TestCase] public void TestA() { TestContext.WriteLine($"A method. setupStr={setupStr}, teardownStr={teardownStr}"); Assert.Pass(); } [TestCase] public void TestB() { TestContext.WriteLine($"B method. setupStr={setupStr}, teardownStr={teardownStr}"); Assert.Pass(); } }実行結果√ TestA() [17ms] 標準出力メッセージ: A method. setupStr=OneTimeSetUp処理, teardownStr= √ TestB() [< 1ms] 標準出力メッセージ: B method. setupStr=OneTimeSetUp処理, teardownStr=少し例が分かり辛いが、
TestAメソッド
、TestBメソッド
実行時にメンバ変数setupStr
に値が代入されていて、メンバ変数teardownStr
には値が入っていないことが分かる。
これは、テストメソッド実行時のタイミングでは、OneTimeSetUpメソッドが一度だけ実行されており、OneTimeTearDownメソッドはまだ実行されていないためだ。
(クラスが持つ全てのテストメソッド実行後にOneTimeTearDownメソッドが呼び出される。)※OneTimeSetUpメソッド、OneTimeTearDownメソッド内で標準出力しても
dotnet test
コマンド実行時に表示されないため。上手いやり方がイマイチ思いつかなかったため、このようなやり方とした。このようにクラス単位のテスト実施前後で一度だけ実行されることから、
テストに使用する外部ファイルの読み込むやDB接続が必要なときのコネクション生成など
各テストメソッドで利用するリソースの準備、および開放などは
このAttributeで実施した方がテスト実行効率が良くなるだろう。3. SetUpFixture
namespace単位で前後の共通処理を定義したい場合に使用するAttribute。
[SetUpFixture]
を付けたクラスが存在する場合、このクラスのnamespace配下のテストが実行される前後で
このクラスが持つOneTimeSetUpメソッド、OneTimeTearDownメソッドが一度だけ実行される。SetUpFixture実行例namespace SetUpFixturePractice { [SetUpFixture] public class MySetUpClass { public static Dictionary<string, object> Config = new Dictionary<string, object>(); public static string SettingKeyA = "setting-A"; public static string SettingKeyB = "setting-B"; public static string SettingKeyC = "setting-C"; [OneTimeSetUp] public void RunBeforeAnyTests() { Config.Add(SettingKeyA,"aaa"); Config.Add(SettingKeyB,"bbb"); Config.Add(SettingKeyC,"ccc"); } [OneTimeTearDown] public void RunAfterAnyTests() { Config.Clear(); } } public class SetUpFixtureAttributesTestA { [TestCase] public void TestA() { TestContext.WriteLine($"A method. settings={MySetUpClass.Config[MySetUpClass.SettingKeyA]}"); Assert.Pass(); } } public class SetUpFixtureAttributesTestB { [TestCase] public void TestB() { TestContext.WriteLine($"B method. settings={MySetUpClass.Config[MySetUpClass.SettingKeyB]}"); Assert.Pass(); } } }実行結果√ TestA() [20ms] 標準出力メッセージ: A method. settings=aaa √ TestB() [< 1ms] 標準出力メッセージ: B method. settings=bbb上記結果を見てわかるとおり、
SetUpFixtureAttributesTestAクラス
およびSetUpFixtureAttributesTestBクラス
のテストメソッド実行時にMySetUpClass.RunBeforeAnyTestsメソッド
でセットされた値が取り出せていることが分かる。
これは、namespace:SetUpFixturePractice
の配下のテストが実行される前にMySetUpClassクラス
のOneTimeSetUpメソッドが呼び出されているためだ。
また、テスト実行後、MySetUpClass.RunAfterAnyTestsメソッド
が呼ばれている。(上記例では分からないが)このようにnamespace単位で事前・事後の処理を定義できる。
テスト全体の共通処理はここに書くと良いだろう。まとめ
事前・事後処理メソッドはテスト実施時のパフォーマンスに影響するので
上手いこと活用したいところ。
メソッド単位、クラス単位、namespace単位で何を事前・事後処理させるかの設計手腕が問われそう。
- 投稿日:2020-11-08T07:30:47+09:00
WPFの非同期復帰の仕組み(Async/Await/Dispatch.BeginInvoke)
「未だにWPF使ってるのかよー」って感じですが許してください…UWPはSandbox環境の制約が面倒すぎるのです。
ここでは以下のように言葉を使います。
- UIスレッド,メインスレッド := Mainメソッドが叩かれたスレッド
- ワーカースレッド := メインスレッド以外のスレッド
非同期処理の要、
Dispatch.BeginInvoke
Dispatch.BeginInvoke
はUIスレッド以外でUIスレッドでしか呼べないメソッドを呼び出したいときに使います。protected override void OnInitialized(EventArgs e) { // メインスレッド new Thread(() => { // ワーカースレッド for(int i = 0; i < 100000000; i++) result += result; // ハチャメチャ重い処理 Dispatch.BeginInvoke(() => // スレッドセーフでないUI操作をキュー { // メインスレッド DisplayToUI(); // UIスレッドでしか呼べないメソッドを呼び出し }); // ワーカースレッド }); }WPFをはじめGUIプラットフォームの多くは、UIをメインスレッドからしか実行できない仕組みになっています。(スレッドセーフでない)
しかし、どうしても次の画面再描画に間に合わない(1/60秒で終わらない)ほど長い処理というものは出てきます。メインスレッドがそういった処理に占有されている間、GUIを書き直すことができないので所謂「フリーズ」を起こしてしまいます。このような問題を回避するために、GUIプラットフォームでは重たい処理は別スレッドに回すというアプローチが見られます。そうすることでUIスレッドを遊ばせることができ、フリーズを回避できます。
もっとも単純な実装のイメージは次のようになります。
static void Main(string[] _) { var uiThreadQueue = new Queue<Action>(); // メインスレッド new Thread(() => { // ワーカースレッド for(int i = 0; i < 100000000; i++) result += result; // ハチャメチャ重い処理 uiThreadQueue.Enqueue(() => // スレッドセーフでないUI操作をキュー { // メインスレッド DisplayToUI(); }); // ワーカースレッド }); // メインスレッド while(true) { while(uiThreadQueue.Count > 0) // ワーカースレッドの完了を監視 { uiThreadQueue.Dequeue().Invoke(); } Repaint(); // UIの更新(これが60回/秒以上実行されればフリーズはしない) } }力業のようにも見えるのですが、実際ほとんど同等の実装になっています。
ポイントは以下の点です。
- UIスレッドに任せたい処理のキューがある
- UIスレッドは初期化処理が終わったら半無限のループでキューを監視している
- 重い処理をワーカースレッドで実行
- ワーカースレッドからUIスレッドにキューを通して処理を任せる
Dispatch
クラスを見てみる
Dispatch
クラスは先述のポイントのうち、1/2/4の役割を担っています。いろいろな例外処理が挟まっているものの、いかのメンバが機能を実装しています。
機能 実装 キュー Dispatch._queue
無限ループ Dispatch.Run
さらに奥の PushFrameImpl
にwhileループがある任せる Dispatch.BeginInvoke
Dispatch._queue
に積むだけちなみに
Dispatch.Run
については、Application.Run
から呼び出され、Application.Run
はMain
から呼び出されます。WPFでは普通見ないのですが、Main
メソッドは当然あります。public partial class App : System.Windows.Application { public static void Main() { HogeTest.App app = new HogeTest.App(); app.InitializeComponent(); app.Run(); // このメソッドはアプリ終了まで抜けることはない } }これでWPFのDispatchの仕組みを完全に理解することができました。しかし、C#にはAsyncAwaitの構文があり
Dispatch.BeginInvoke
を使うことは非常に稀であります。ということでWPFのAsyncAwaitも見ていきましょう。AsyncAwaitの動作の仕組み
詳しくは以前に書いたこちら(async/await で書いたコードと実行されるスレッド)を見ていただきたいのですが、ざっくりと説明すると。
await HogeAsync()
描いたとしましょう。すると、その直前のSynchronizationContext(≒スレッド)を記憶しておき、HogeAsyncの中で「よびだし元のSynchronizationContextで実行してくれな処理パート」が出てきた時に記憶しておいたSynchronizationContextで処理を行うというものです。コードにするとこんなイメージ。(実際にはコンパイラの仕事が挟まれるのであくまでイメージ){ ui.text = "aaaa"; var x = await GetTextAsync(); ui.text = text; await Task.Run(() => Thread.Sleep(100000)); ui.text = ""; } // ↓ { SynchronizationContext callerContext = SynchronizationContext.Current; ui.text = "aaaa"; GetTextAsync(onComplete: text => { callerContext.Post(() => // 呼び出し元のContext(≒スレッド)に戻す { ui.text = text; Task.Run(() => { // Task.Runはワーカースレッドで実行するため、ここはメインスレッドではなくなっている Thread.Sleep(100000) // onComplete(); callerContext.Post(() => // 呼び出し元のContext(≒スレッド)に戻す { ui.text = ""; }); }); }; }); }ということで、awaitをうまく使うためには、
SynchronizationContext.Current
(Staticなのでプロセス内で共有の設定です)のインスタンスに最低でもSynchronizationContext.Post
が実装されている必要があります。(実際にはもっといろいろ必要)
そしてPost
にコールバックを渡したときにメインスレッドで実行されるようになっていればOKです。
ちなみにConsoleアプリではSynchronizationContext.Current
はデフォルトでnullであり、awaitから戻ってきてもメインスレッドに復帰しません。WPFでは、この
SynchronizationContext
がDispatcherSynchronizationContext
にて実装されています。そして肝心のPost
については、Dispatcher.BeginInvoke
が呼ばれるようになっています。まとめ
ということで紐解いてみると結構あっけない作りだったりします。まぁ独自のGUIシステムを作ろうとでもしない限り、何も考えなくても書けますけどね。
- 投稿日:2020-11-08T07:30:47+09:00
WPFの非同期復帰の実装を覗いてみた(Async/Await/Dispatch.BeginInvoke)
「未だにWPF使ってるのかよー」って感じですが許してください…UWPはSandbox環境の制約が面倒すぎるのです。
ここでは以下のように言葉を使います。
- UIスレッド,メインスレッド := Mainメソッドが叩かれたスレッド
- ワーカースレッド := メインスレッド以外のスレッド
非同期処理の要、
Dispatch.BeginInvoke
Dispatch.BeginInvoke
はUIスレッド以外でUIスレッドでしか呼べないメソッドを呼び出したいときに使います。protected override void OnInitialized(EventArgs e) { // メインスレッド new Thread(() => { // ワーカースレッド for(int i = 0; i < 100000000; i++) result += result; // ハチャメチャ重い処理 Dispatch.BeginInvoke(() => // スレッドセーフでないUI操作をキュー { // メインスレッド DisplayToUI(); // UIスレッドでしか呼べないメソッドを呼び出し }); // ワーカースレッド }); }WPFをはじめGUIプラットフォームの多くは、UIをメインスレッドからしか実行できない仕組みになっています。(スレッドセーフでない)
しかし、どうしても次の画面再描画に間に合わない(1/60秒で終わらない)ほど長い処理というものは出てきます。メインスレッドがそういった処理に占有されている間、GUIを書き直すことができないので所謂「フリーズ」を起こしてしまいます。このような問題を回避するために、GUIプラットフォームでは重たい処理は別スレッドに回すというアプローチが見られます。そうすることでUIスレッドを遊ばせることができ、フリーズを回避できます。
もっとも単純な実装のイメージは次のようになります。
static void Main(string[] _) { var uiThreadQueue = new Queue<Action>(); // メインスレッド new Thread(() => { // ワーカースレッド for(int i = 0; i < 100000000; i++) result += result; // ハチャメチャ重い処理 uiThreadQueue.Enqueue(() => // スレッドセーフでないUI操作をキュー { // メインスレッド DisplayToUI(); }); // ワーカースレッド }); // メインスレッド while(true) { while(uiThreadQueue.Count > 0) // ワーカースレッドの完了を監視 { uiThreadQueue.Dequeue().Invoke(); } Repaint(); // UIの更新(これが60回/秒以上実行されればフリーズはしない) } }力業のようにも見えるのですが、実際ほとんど同等の実装になっています。
ポイントは以下の点です。
- UIスレッドに任せたい処理のキューがある
- UIスレッドは初期化処理が終わったら半無限のループでキューを監視している
- 重い処理をワーカースレッドで実行
- ワーカースレッドからUIスレッドにキューを通して処理を任せる
Dispatcher
クラスを見てみる
Dispatcher
クラスは先述のポイントのうち、1/2/4の役割を担っています。いろいろな例外処理が挟まっているものの、いかのメンバが機能を実装しています。
機能 実装 キュー Dispatch._queue
無限ループ Dispatch.Run
さらに奥の PushFrameImpl
にwhileループがある任せる Dispatch.BeginInvoke
Dispatch._queue
に積むだけちなみに
Dispatch.Run
については、Application.Run
から呼び出され、Application.Run
はMain
から呼び出されます。WPFでは普通見ないのですが、Main
メソッドは当然あります。public partial class App : System.Windows.Application { public static void Main() { HogeTest.App app = new HogeTest.App(); app.InitializeComponent(); app.Run(); // このメソッドはアプリ終了まで抜けることはない } }これでWPFのDispatchの仕組みを完全に理解することができました。しかし、C#にはAsyncAwaitの構文があり
Dispatch.BeginInvoke
を使うことは非常に稀であります。ということでWPFのAsyncAwaitも見ていきましょう。AsyncAwaitの動作の仕組み
詳しくは以前に書いたこちら(async/await で書いたコードと実行されるスレッド)を見ていただきたいのですが、ざっくりと説明すると。
await HogeAsync()
描いたとしましょう。すると、その直前のSynchronizationContext(≒スレッド)を記憶しておき、HogeAsyncの中で「よびだし元のSynchronizationContextで実行してくれな処理パート」が出てきた時に記憶しておいたSynchronizationContextで処理を行うというものです。コードにするとこんなイメージ。(実際にはコンパイラの仕事が挟まれるのであくまでイメージ){ ui.text = "aaaa"; var x = await GetTextAsync(); ui.text = text; await Task.Run(() => Thread.Sleep(100000)); ui.text = ""; } // ↓ { SynchronizationContext callerContext = SynchronizationContext.Current; ui.text = "aaaa"; GetTextAsync(onComplete: text => { callerContext.Post(() => // 呼び出し元のContext(≒スレッド)に戻す { ui.text = text; Task.Run(() => { // Task.Runはワーカースレッドで実行するため、ここはメインスレッドではなくなっている Thread.Sleep(100000) // onComplete(); callerContext.Post(() => // 呼び出し元のContext(≒スレッド)に戻す { ui.text = ""; }); }); }; }); }ということで、awaitをうまく使うためには、
SynchronizationContext.Current
(Staticなのでプロセス内で共有の設定です)のインスタンスに最低でもSynchronizationContext.Post
が実装されている必要があります。(実際にはもっといろいろ必要)
そしてPost
にコールバックを渡したときにメインスレッドで実行されるようになっていればOKです。
ちなみにConsoleアプリではSynchronizationContext.Current
はデフォルトでnullであり、awaitから戻ってきてもメインスレッドに復帰しません。WPFでは、この
SynchronizationContext
がDispatcherSynchronizationContext
にて実装されています。そして肝心のPost
については、Dispatcher.BeginInvoke
が呼ばれるようになっています。これはちょっとよくわからなかったのですが、
SynchronizationContext.SetSynchronizationContext
でDispatcherSynchronizationContext
を設定している個所が、Dispatcher.Invoke
したタイミング以外見当たらないんですよね…
つまりそれまではawaitしても帰ってくることができなくなりそうです。一回でもDispatcher.Invoke
が呼ばれていればそれ以降はCurrentにContextが入り続けるので、WPFのコアが初期化している間にDispatcher.Invoke
が呼ばれていい感じになるのかな…という感じです。しかし、普通に考えたら明示的に初期化するでしょって思っちゃうのですが、私が見落としているだけなんでしょうかね???まとめ
ということで紐解いてみると結構あっけない作りだったりします。まぁ独自のGUIシステムを作ろうとでもしない限り、何も考えなくても書けますけどね。
- 投稿日:2020-11-08T01:17:47+09:00
ビットコインの秘密鍵、公開鍵、ビットコインアドレスをサクッと生成する
使用環境
- Visual Studio Code https://code.visualstudio.com/
- .Net Core https://www.microsoft.com/net/download/core
- C# https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp
実施方法
Visual Studio Codeで以下のコマンドを実行する。
# 任意のプロジェクトフォルダを用意 mkdir MyProject cd MyProject # プロジェクトの開始と必要パッケージ(NBitcoin)の追加 dotnet new console dotnet add package NBitcoin dotnet restore秘密鍵、公開鍵、ビットコインアドレスを生成するコードを実行する。
using System; using NBitcoin; namespace MyProject { class Program { static void Main(string[] args) { int n = 3; for (int i = 1; i <= n; i++) { // Mainnet or Testnet var network = Network.GetNetwork("testnet"); // Generate Keys var privateKey = new Key(); PubKey publicKey = privateKey.PubKey; Console.WriteLine(i); Console.WriteLine("private key (WIF): " + privateKey.GetWif(network)); Console.WriteLine("public key: " + publicKey.ToString()); Console.WriteLine("bitcoin address: " + publicKey.GetAddress(ScriptPubKeyType.Legacy, network).ToString()); } } } }※上記の例ではtestnetを使用しています。testnetは開発やテスト用のネットワークで、そこでやりとりされるビットコインは無価値です。
実行結果
1 private key (WIF): cT7uPMYW53wF99zoLwuTYbFX9u64pZbfMRZPzR4x5ters1imZteK public key: 026a44900a5adc5e0cb9e3b68bec8dc830a95246eaa449e1982209a61a850011c3 bitcoin address: mmDSo9ezSiAXmasfDFsiLjMTbkALNS37wW 2 private key (WIF): cPoEcLELGdXjHK5HB4tFxWf11UabeBJX4muBZSTZoavL8ZbDpBGa public key: 02ce3ff023c56e25354574f4f04451d4c2947847360495e490a726f6ce2ac0f15a bitcoin address: n1LkPbkfFT9xSUmSNZ4gBak1Mqe8AKs1n5 3 private key (WIF): cVpjKP3ypD7tGpjRNzbnJoWP2ydGu2e1RZQFGawaPc71wWSbjVxy public key: 0385e93613988781cb323ab8083b724bb870592091cbf0cb2e370f37ea3c4c910e bitcoin address: mm3hoc3H1RAxeg3V5dsTFpiZST5d5CNXyGこれらのビットコインアドレスに宛てて送金を行うと、ブロックチェーン上にトランザクション(取引情報)が書き込まれます。
- 投稿日:2020-11-08T01:17:47+09:00
C#でビットコインの秘密鍵、公開鍵、ビットコインアドレスをサクッと生成する
使用環境
- Visual Studio Code https://code.visualstudio.com/
- .Net Core https://www.microsoft.com/net/download/core
- C# https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp
実施方法
Visual Studio Codeで以下のコマンドを実行する。
# 任意のプロジェクトフォルダを用意 mkdir MyProject cd MyProject # プロジェクトの開始と必要パッケージ(NBitcoin)の追加 dotnet new console dotnet add package NBitcoin dotnet restore秘密鍵、公開鍵、ビットコインアドレスを生成するコードを実行する。
using System; using NBitcoin; namespace MyProject { class Program { static void Main(string[] args) { int n = 3; for (int i = 1; i <= n; i++) { // Mainnet or Testnet var network = Network.GetNetwork("testnet"); // Generate Keys var privateKey = new Key(); PubKey publicKey = privateKey.PubKey; Console.WriteLine(i); Console.WriteLine("private key (WIF): " + privateKey.GetWif(network)); Console.WriteLine("public key: " + publicKey.ToString()); Console.WriteLine("bitcoin address: " + publicKey.GetAddress(ScriptPubKeyType.Legacy, network).ToString()); } } } }※上記の例ではtestnetを使用しています。testnetは開発やテスト用のネットワークで、そこでやりとりされるビットコインは無価値です。
実行結果
1 private key (WIF): cT7uPMYW53wF99zoLwuTYbFX9u64pZbfMRZPzR4x5ters1imZteK public key: 026a44900a5adc5e0cb9e3b68bec8dc830a95246eaa449e1982209a61a850011c3 bitcoin address: mmDSo9ezSiAXmasfDFsiLjMTbkALNS37wW 2 private key (WIF): cPoEcLELGdXjHK5HB4tFxWf11UabeBJX4muBZSTZoavL8ZbDpBGa public key: 02ce3ff023c56e25354574f4f04451d4c2947847360495e490a726f6ce2ac0f15a bitcoin address: n1LkPbkfFT9xSUmSNZ4gBak1Mqe8AKs1n5 3 private key (WIF): cVpjKP3ypD7tGpjRNzbnJoWP2ydGu2e1RZQFGawaPc71wWSbjVxy public key: 0385e93613988781cb323ab8083b724bb870592091cbf0cb2e370f37ea3c4c910e bitcoin address: mm3hoc3H1RAxeg3V5dsTFpiZST5d5CNXyGこれらのビットコインアドレスに宛てて送金を行うとブロックチェーン上にトランザクション(取引情報)が書き込まれます。コインを取得できたら他のアドレスに宛てた送金が行えるようになります。
- 投稿日:2020-11-08T01:17:47+09:00
C#でビットコインの秘密鍵、公開鍵、ビットコインアドレスを生成する
使用環境
実施方法
Visual Studio Codeで以下のコマンドを実行する。
# 任意のプロジェクトフォルダを用意 mkdir MyProject cd MyProject # プロジェクトの開始と必要パッケージ(NBitcoin)の追加 dotnet new console dotnet add package NBitcoin dotnet restore秘密鍵、公開鍵、ビットコインアドレスを生成するコードを実行する。
using System; using NBitcoin; namespace MyProject { class Program { static void Main(string[] args) { int n = 3; for (int i = 1; i <= n; i++) { // Mainnet or Testnet var network = Network.GetNetwork("testnet"); // Generate Keys var privateKey = new Key(); PubKey publicKey = privateKey.PubKey; Console.WriteLine(i); Console.WriteLine("private key (WIF): " + privateKey.GetWif(network)); Console.WriteLine("public key: " + publicKey.ToString()); Console.WriteLine("bitcoin address: " + publicKey.GetAddress(ScriptPubKeyType.Legacy, network).ToString()); } } } }※上記の例ではtestnetを使用しています。testnetは開発やテスト用のネットワークで、そこでやりとりされるビットコインは無価値です。
実行結果
生成された秘密鍵、公開鍵、ビットコインアドレスの情報。
1 private key (WIF): cT7uPMYW53wF99zoLwuTYbFX9u64pZbfMRZPzR4x5ters1imZteK public key: 026a44900a5adc5e0cb9e3b68bec8dc830a95246eaa449e1982209a61a850011c3 bitcoin address: mmDSo9ezSiAXmasfDFsiLjMTbkALNS37wW 2 private key (WIF): cPoEcLELGdXjHK5HB4tFxWf11UabeBJX4muBZSTZoavL8ZbDpBGa public key: 02ce3ff023c56e25354574f4f04451d4c2947847360495e490a726f6ce2ac0f15a bitcoin address: n1LkPbkfFT9xSUmSNZ4gBak1Mqe8AKs1n5 3 private key (WIF): cVpjKP3ypD7tGpjRNzbnJoWP2ydGu2e1RZQFGawaPc71wWSbjVxy public key: 0385e93613988781cb323ab8083b724bb870592091cbf0cb2e370f37ea3c4c910e bitcoin address: mm3hoc3H1RAxeg3V5dsTFpiZST5d5CNXyGこれらのビットコインアドレスに宛てて送金を行うとブロックチェーン上にトランザクション(取引情報)が書き込まれます。コインを取得できたら他のアドレスに宛てた送金が行えるようになります。