問題の声明
たとえば、このような構造のRegionクラスがあります
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
public class Region { public string Name { get ; set ; } public int Index { get ; set ; } public IEnumerable <Region> SubRegions { get ; set ; } } * This source code was highlighted with Source Code Highlighter .
また、このモデルに従って構築された領域のツリーをUIでユーザーに表示するタスクがあり、さらに処理が必要な特定の領域を選択する機会を与えます。 MVVMアーキテクチャでUIを作成している場合、 Regionクラスに似た構造を持つIsSelectedプロパティが追加されたRegionViewModel型のクラスを作成する必要があることは明らかです。 さらに、 RegionRegionのSubRegionsプロパティがIEnumerable <RegionViewModel>型の列挙子を返した場合、すべてのUIコードが、 RegionViewModelリストでツリービューツリービューを作成し、 IsSelectedプロパティがtrueであるすべてのRegionViewModelを取得する関数になります 。 しかし、問題は何ですか? 問題は、 RegionViewModelクラスのコードのほとんどが、データをRegionクラスに転送し、それをラップすることです。 もちろん、このような実装は可能です。
*このソースコードは、 ソースコードハイライターで強調表示されました。
- パブリック クラス RegionViewModel
- {
- パブリックリージョン値{ get ; セット ; }
- public IEnumerable <RegionViewModel> SubRegions { get { /*...*/ }}
- }
ただし、 SubRegionsプロパティを手作業で実装する必要もあります。 また、 RegionがINotifyPropertyChangedを実装していない場合、ほとんどの場合、各プロパティを自分の手で記述し、対応するイベントへの呼び出しを追加して値を設定する必要があります。
上記のようなタスクが頻繁に発生する場合は、毎回手でビューモデルを作成すると疲れる可能性があります。 したがって、私はこのプロセスの自動化の質問をしました、そして、これは何が起こったかです。
可能な解決策
よく見ると、タスクが特定のクラスのプロキシを作成する特定のプロキシジェネレーターを作成し、それをINotifyPropertyChanged 、または/などのいくつかの側面で補完し、新しいプロパティ、メソッドなどを追加することが明らかになります。 .netスタックは、プロキシを作成するために何を提供しますか?
次のソリューションを区別できます
- Castle DynamicProxy
- MSのRealProxy
- T4ベースのコード生成
- PostSharpを使用して、コードインストルメンテーションに基づいて何かを作成します。
- 使用はC#4.0ダイナミックで登場
5番目のオプションを選択したのは、 RealProxyはモデルクラス自体への介入を必要とします。城からのプロキシはクラスの仮想メンバーのみをインターセプトし、シャープ後の費用はかかります。宗教上の理由からコード生成は好きではありません。 さらに、独自の自転車を作成することは常に興味深いものです。
実装
そのため、主なアイデアは次のとおりです。DynamicObject型から継承し、 TryInvokeMember 、 TrySetMember 、およびTryGetMemberをオーバーライドするクラスを記述します。
例えば
*このソースコードは、 ソースコードハイライターで強調表示されました。
- public override bool TryInvokeMember(InvokeMemberBinderバインダ、 オブジェクト [] args、 out オブジェクトの結果)
- {
- result = _methods.ContainsKey(binder.Name)? _methods [binder.Name] .DynamicInvoke( new [] { this } .Concat(args).ToArray()):InvokeNativeMethod(binder.Name、args);
- 結果= GetResult(結果);
- trueを 返し ます 。
- }
入力で目的のオブジェクトを受け入れ、クラスのメンバーを呼び出そうとすると、彼はこのオブジェクトに呼び出しを転送します。 これに、新しいメソッドとプロパティを追加する機能を追加します。 オブジェクトを構築するには、Fluent Builderパターンを使用します。
プロキシクラスコード自体は非常に大きい(約280行)ことが判明したため、それを見たい人は投稿の下部にあるリンクからソースとすべての例をダウンロードできます。 ここで使用例を示します。
プロパティを追加する
*このソースコードは、 ソースコードハイライターで強調表示されました。
- プライベート クラス MyClass
- {
- MyClass _i;
- パブリック ストリング Name { get ; セット ; }
- パブリック MyClass Foo()
- {
- return _i ?? (_i = 新しい MyClass {Name = "_sdfdsfsdfsfd" });
- }
- public IEnumerable <MyClass> GetChilds()
- {
- yield return new MyClass();
- yield return new MyClass();
- }
- }
- [テスト方法]
- public void TestAddProperties()
- {
- var a = new MyClass {Name = "123" };
- Assert.AreEqual( "123" 、a.Name);
- 動的プロキシ= DynamicProxy.Create(a).AddProperty < bool >( "IsSelected" )
- .AddProperty( "X" 、_ => x、(_、 value )=> x = value )
- .AddProperty( "LastName" 、 "FFFF" )
- proxy.Name = "567" ;
- proxy.IsSelected = true ;
- proxy.X = 42;
- Assert.AreEqual( "567" 、a.Name);
- Assert.IsTrue(proxy.IsSelected);
- Assert.AreEqual(42、x);
- proxy.IsSelected = false ;
- Assert.IsFalse(proxy.IsSelected);
- }
関数およびプロパティ値のプロキシを自動作成
*このソースコードは、 ソースコードハイライターで強調表示されました。
- [テスト方法]
- public void TestChilds()
- {
- var a = new MyClass {Name = "123" };
- Assert.AreEqual( "123" 、a.Name);
- Assert.AreEqual( "_sdfdsfsdfsfd" 、a.Foo()。名前);
- var x = 0;
- 動的プロキシ= DynamicProxy.Create(a).AddProperty < bool >( "IsSelected" )
- .AddProperty( "X" 、_ => x、(_、 value )=> x = value )
- .AddProperty( "LastName" 、 "FFFF" )
- .AddMethod( "Boo" 、 new Func <DynamicProxy <MyClass>、 int 、 string >((m、i)=>((MyClass)m).Name + i.ToString()));
- proxy.Name = "567" ;
- proxy.IsSelected = true ;
- proxy.X = 42;
- var b = proxy.Foo();
- b.IsSelected = true ;
- Assert.AreEqual( "567" 、a.Name);
- Assert.AreEqual( "5674" 、proxy.Boo(4));
- Assert.IsTrue(proxy.IsSelected);
- Assert.AreEqual(42、x);
- Assert.IsTrue(b.IsSelected);
- b.IsSelected = false ;
- Assert.IsTrue(proxy.IsSelected);
- Assert.IsFalse(b.IsSelected);
- proxy.LastName = "890" ;
- var d = proxy.Foo();
- Assert.AreEqual( "FFFF" 、d.LastName);
- var d2 = proxy.Foo();
- d2.LastName = "RRRRR" ;
- Assert.AreEqual( "567" 、Foo(プロキシ));
- Assert.AreEqual(d.LastName、d2.LastName);
- //
コロバンを奪って、完璧なカーストを作ることができます- var c =(MyClass)プロキシ;
- Assert.AreEqual( "567" 、c.Name);
- foreach (proxy.GetChilds()のvar child)
- {
- child.IsSelected = true ;
- Assert.IsTrue(child.IsSelected);
- }
- }
INotifyPropertyChangedの自動実装も行われました。
*このソースコードは、 ソースコードハイライターで強調表示されました。
- [テスト方法]
- public void TestPropertyChange()
- {
- var myClass = new MyClass();
- var propertyName = string .Empty;
- 動的プロキシ= DynamicProxy.Create(myClass);
- ((INotifyPropertyChanged)プロキシ).PropertyChanged + =(s、a)=> propertyName = a.PropertyName;
- proxy.Name = "aaaa" ;
- Assert.AreEqual( "Name" 、propertyName);
- }
実用化
このプロキシを使用して問題を解決してみましょう。これについては、記事の冒頭で説明します。 単純化するために、領域の小さなツリーを生成し、ユーザーに必要なものを選択させ、選択したもののリストを右側に表示させます。 実際には、ウィンドウコードは次のようになります
*このソースコードは、 ソースコードハイライターで強調表示されました。
- パブリック MainWindow()
- {
- InitializeComponent();
- DataContext = this ;
- Items = new [] {DynamicProxy.Create(CreateRegions()。First())。AddProperty < bool >( "IsSelected" )};
- }
- IEnumerable <Region> GetSelectedItems( IEnumerable <dynamic>アイテム)
- {
- return items.Where(x => x.IsSelected).Concat(items.SelectMany(x => GetSelectedItems(( IEnumerable <dynamic>)x.SubRegions)))。Select(x =>(Region)x);
- }
- private void ButtonClick( オブジェクト送信者、RoutedEventArgs e)
- {
- var res = GetSelectedItems(アイテム).Take(10).ToList();
- SelectedItems = res;
- }
そして彼にxaml
*このソースコードは、 ソースコードハイライターで強調表示されました。
- < Window x:Class = "WpfApplication1.MainWindow"
- xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication1 = "clr-namespace:WpfApplication1"
- タイトル = "MainWindow" 高さ = "350" 幅 = "525" >
- < Window.Resources >
- < DataTemplate DataType = "{x:Type WpfApplication1:Region}" >
- < WrapPanel >
- < TextBlock Text = "{Binding Path = Name、StringFormat = '{} {0}、'}" />
- < TextBlock Text = "{Binding Path = Index}" />
- </ WrapPanel >
- </ DataTemplate >
- </ Window.Resources >
- < グリッド >
- < Grid.ColumnDefinitions >
- < ColumnDefinition Width = "*" />
- < ColumnDefinition Width = "auto" />
- < ColumnDefinition Width = "*" />
- </ Grid.ColumnDefinitions >
- < TreeView グリッド 。 列 = "0" ItemsSource = "{Binding Items}" BorderThickness = "0" >
- < TreeView.ItemTemplate >
- < HierarchicalDataTemplate ItemsSource = "{Binding SubRegions}" >
- < CheckBox IsChecked = "{Binding IsSelected、Mode = TwoWay}" Content = "{Binding Value}" />
- </ HierarchicalDataTemplate >
- </ TreeView.ItemTemplate >
- </ TreeView >
- < ボタンの 内容 =「選択を表示」 VerticalAlignment =「中央」 グリッド 。 列 = "1" クリック = "ButtonClick" />
- < ListBox グリッド 。 列 = "2" ItemsSource = "{Binding SelectedItems}" BorderThickness = "0" />
- </ グリッド >
- </ ウィンドウ >
アプリケーションのスクリーンショット
参照資料
ご清聴ありがとうございました。