ViewService.Wpf – ViewModelからViewの機能を呼び出す

WPF で ViewModel から View の機能を呼び出すための ViewService.Wpf というライブラリを作成しました。

実際のView をサービスという形で ViewModel に公開し ViewModel はそのサービスを通して View の機能を呼び出せるため、ViewModel は View への依存を極力持たないように出来ます。

使い方

初めに ViewService.Wpf の簡単な使い方を説明します。

View に使用するサービスを定義

View のコードビハインドか、XAML のリソースに使いたいサービスを定義します。
次の例では、XAML でリソースに定義しています。

<Window xmlns:service="http://schemas.lumiria.com/view-services">
<ResourceDictionary>
<service:ViewServiceProvider x:Key="ViewServiceProvider">
<service:WindowService Key="Sub"
WindowType="{x:Type view:SubWindow}"
Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
<service:MessageBoxService Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
</service:ViewServiceProvider>
</ResourceDictionary>
</Window.Resources>
<!-- 省略 -->
</Window>

ViewModel でサービスを取得

View で定義した ViewServiceProvider を ViewModel に渡し、ViewModel はこの ViewServiceProvider から対象のサービスを取得します。
あとはサービスを通して View の機能を呼び出します。

var windowService = provider.Get<IWindowService>("Sub");
windowService.ShowDialog();

実際の使い方は、このサンプルを参考にしてください。

実装しているサービス

Ver 1.0.0 では次のサービスを実装しています。

WindowService

Window を表示するためのサービスです。

WindowActionService

対象の Window へのアクションを実行するサービスです。
ウィンドウを閉じたり、最大・最小化することが出来ます。

MessageBoxService

メッセージボックスを表示するためのサービスです。

まとめ

View が起点の処理ならば積極的にコードビハインドに実装すれば良いですが、どうしても ViewModel から View にアクセスしたいケースはあるかと思います。
そういうときはサービスやメッセンジャーといったパターンを使用すれば、ViewModel が View に直接依存するのを避けることが出来ます。

個人的には、メッセンジャーによる実装が一番良いと考えていますが、あまり手軽ではないように感じます。
ViewService.Wpf を使えば、利用頻度が高いと思われる View の機能をサービスとしてすぐに使うことが出来ます。

DeepCopy.Expression – C#でディープコピー

  • 2023/5/31 内容を修正

C# でオブジェクトをディープコピーする DeepCopy.Expression というライブラリを作りました。
ディープコピーとは、オブジェクトの中に含まれるすべてのメンバや参照先のオブジェクトも新しく作成してコピーすることです。
ディープコピーするには、一般的にはシリアライズとデシリアライズを使う方法がありますが、これは速度や柔軟性に問題があります。
DeepCopy.Expression は、対象のオブジェクトの型ごとに式木を使って動的にコードを生成し、キャッシュすることで高速にコピーを行います。また [Cloneable] 属性を使ってコピーの挙動をカスタマイズすることもできます。

使い方

DeepCopy.Expression の使い方はとても簡単です。ObjectCloner クラスの Clone メソッドか CopyTo メソッドを呼び出すだけです。
Clone メソッドは、引数に渡したオブジェクトのディープコピーを作成して返します。CopyTo メソッドは、第一引数に渡したオブジェクトの内容を第二引数に渡したオブジェクトにコピーします。どちらのメソッドも、対象のオブジェクトが配列や匿名型であっても問題ありません。

例えば、以下のようなコードでディープコピーすることができます。

var target = new TestObject(); // コピーするオブジェクト
var cloned = ObjectCloner.Clone(target); // ディープコピーを作成

// 又は
TestObject destination;
ObjectCloner.CopyTo(target, destination); // ディープコピーを行う

パフォーマンス

DeepCopy.Expression のパフォーマンスは、他のライブラリと比べても優れています。以下は、同じオブジェクトを異なる回数だけディープコピーしたときの処理時間を比較したものです。

ライブラリ1回100回2,500回10,000回
A97 msec105 msec120 msec130 msec
B28 msec32 msec41 msec50 msec
C0 msec0 msec13 msec38 msec
DeepCopy.Expression27 msec28 msec31 msec38 msec

この結果からわかるように、DeepCopy.Expression は初回以降のディープコピーでは高速です。初回では式木の生成とコンパイルが行わるため、他のライブラリより遅くなりますが、その後はキャッシュされたコードで高速に処理されます。

コピーに使用したのは次のようなオブジェクトです。

using 

class TestObject
{
    int _num;
    string _name;
    DateTime _date;
    int[] _array;
 
    public static TestObject Create()
    {
        var random = new Random(Environment.TickCount);

        var instance = new TestObject();
        instance._num = random.Next();
        instance._name = random.Next().ToString();
        instance._date = DateTime.Now;
        instance._array = new int[1024];
        for (int i = 0; i < 1024; ++i)
            instance._array[i] = random.Next();

        return instance;
    }
}

尚、 Ver 1.1.0 からは事前にキャッシュを生成しておく機能を実装しています。こうすることで、初回のコピー時から高速に動作させることが可能になっています。

まとめ

同じ型のオブジェクトを繰り返しディープコピーする必要があり、速度が求められるようなケースでは DeepCopy.Expression は有用ではないでしょうか。アプリの初期化時等で事前にキャッシュ生成しておけるならば、型毎に専用のディープコピーコードを書いた時と同等近い速度が得られます。

DeepCopy.Expression は、NuGetからダウンロードできます。詳細な使い方やソースコードは GitHub で確認できます。

先頭に戻る