動的タイプを使用した動的プロキシオブジェクトの作成

アプリケーション用に別のUIを作成するタスクに直面している多くの人々と同様、私はUIの独自のモデルを作成する必要性に定期的に遭遇します。 そして、これはそれから来たものです。



問題の声明



たとえば、このような構造の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 .



  1. 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 .



  2. 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 .



  3. 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 .



  4. 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 .



  5. 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 .



  6. 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クラスに転送し、それをラップすることです。 もちろん、このような実装は可能です。





  1. パブリック クラス RegionViewModel
  2. {
  3. パブリックリージョン値{ get ; セット ; }
  4. public IEnumerable <RegionViewModel> SubRegions { get { /*...*/ }}
  5. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


ただし、 SubRegionsプロパティを手作業で実装する必要もあります。 また、 RegionINotifyPropertyChangedを実装していない場合、ほとんどの場合、各プロパティを自分の手で記述し、対応するイベントへの呼び出しを追加して値を設定する必要があります。

上記のようなタスクが頻繁に発生する場合は、毎回手でビューモデルを作成すると疲れる可能性があります。 したがって、私はこのプロセスの自動化の質問をしました、そして、これは何が起こったかです。



可能な解決策



よく見ると、タスクが特定のクラスのプロキシを作成する特定のプロキシジェネレーターを作成し、それをINotifyPropertyChanged 、または/などのいくつかの側面で補完し、新しいプロパティ、メソッドなどを追加することが明らかになります。 .netスタックは、プロキシを作成するために何を提供しますか?



次のソリューションを区別できます



5番目のオプションを選択したのは、 RealProxyはモデルクラス自体への介入を必要とします。城からのプロキシはクラスの仮想メンバーのみをインターセプトし、シャープ後の費用はかかります。宗教上の理由からコード生成は好きではありません。 さらに、独自の自転車を作成することは常に興味深いものです。

実装



そのため、主なアイデアは次のとおりです。DynamicObject型から継承し、 TryInvokeMemberTrySetMember 、およびTryGetMemberをオーバーライドするクラスを記述します。

例えば





  1. public override bool TryInvokeMember(InvokeMemberBinderバインダ、 オブジェクト [] args、 out オブジェクトの結果)
  2. {
  3. result = _methods.ContainsKey(binder.Name)? _methods [binder.Name] .DynamicInvoke( new [] { this } .Concat(args).ToArray()):InvokeNativeMethod(binder.Name、args);
  4. 結果= GetResult(結果);
  5. trueを 返し ます
  6. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


入力で目的のオブジェクトを受け入れ、クラスのメンバーを呼び出そうとすると、彼はこのオブジェクトに呼び出しを転送します。 これに、新しいメソッドとプロパティを追加する機能を追加します。 オブジェクトを構築するには、Fluent Builderパターンを使用します。



プロキシクラスコード自体は非常に大きい(約280行)ことが判明したため、それを見たい人は投稿の下部にあるリンクからソースとすべての例をダウンロードできます。 ここで使用例を示します。



プロパティを追加する







  1. プライベート クラス MyClass
  2. {
  3. MyClass _i;
  4. パブリック ストリング Name { get ; セット ; }
  5. パブリック MyClass Foo()
  6. {
  7. return _i ?? (_i = 新しい MyClass {Name = "_sdfdsfsdfsfd" });
  8. }
  9. public IEnumerable <MyClass> GetChilds()
  10. {
  11. yield return new MyClass();
  12. yield return new MyClass();
  13. }
  14. }
  15. [テスト方法]
  16. public void TestAddProperties()
  17. {
  18. var a = new MyClass {Name = "123" };
  19. Assert.AreEqual( "123" 、a.Name);
  20. 動的プロキシ= DynamicProxy.Create(a).AddProperty < bool >( "IsSelected"
  21. .AddProperty( "X" 、_ => x、(_、 value )=> x = value
  22. .AddProperty( "LastName""FFFF"
  23. proxy.Name = "567" ;
  24. proxy.IsSelected = true ;
  25. proxy.X = 42;
  26. Assert.AreEqual( "567" 、a.Name);
  27. Assert.IsTrue(proxy.IsSelected);
  28. Assert.AreEqual(42、x);
  29. proxy.IsSelected = false ;
  30. Assert.IsFalse(proxy.IsSelected);
  31. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


関数およびプロパティ値のプロキシを自動作成







  1. [テスト方法]
  2. public void TestChilds()
  3. {
  4. var a = new MyClass {Name = "123" };
  5. Assert.AreEqual( "123" 、a.Name);
  6. Assert.AreEqual( "_sdfdsfsdfsfd" 、a.Foo()。名前);
  7. var x = 0;
  8. 動的プロキシ= DynamicProxy.Create(a).AddProperty < bool >( "IsSelected"
  9. .AddProperty( "X" 、_ => x、(_、 value )=> x = value
  10. .AddProperty( "LastName""FFFF"
  11. .AddMethod( "Boo"new Func <DynamicProxy <MyClass>、 intstring >((m、i)=>((MyClass)m).Name + i.ToString()));
  12. proxy.Name = "567" ;
  13. proxy.IsSelected = true ;
  14. proxy.X = 42;
  15. var b = proxy.Foo();
  16. b.IsSelected = true ;
  17. Assert.AreEqual( "567" 、a.Name);
  18. Assert.AreEqual( "5674" 、proxy.Boo(4));
  19. Assert.IsTrue(proxy.IsSelected);
  20. Assert.AreEqual(42、x);
  21. Assert.IsTrue(b.IsSelected);
  22. b.IsSelected = false ;
  23. Assert.IsTrue(proxy.IsSelected);
  24. Assert.IsFalse(b.IsSelected);
  25. proxy.LastName = "890" ;
  26. var d = proxy.Foo();
  27. Assert.AreEqual( "FFFF" 、d.LastName);
  28. var d2 = proxy.Foo();
  29. d2.LastName = "RRRRR" ;
  30. Assert.AreEqual( "567" 、Foo(プロキシ));
  31. Assert.AreEqual(d.LastName、d2.LastName);
  32. // コロバン奪って 、完璧なカーストを作ることができます
  33. var c =(MyClass)プロキシ;
  34. Assert.AreEqual( "567" 、c.Name);
  35. foreach (proxy.GetChilds()のvar child)
  36. {
  37. child.IsSelected = true ;
  38. Assert.IsTrue(child.IsSelected);
  39. }
  40. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


INotifyPropertyChangedの自動実装も行われました





  1. [テスト方法]
  2. public void TestPropertyChange()
  3. {
  4. var myClass = new MyClass();
  5. var propertyName = string .Empty;
  6. 動的プロキシ= DynamicProxy.Create(myClass);
  7. ((INotifyPropertyChanged)プロキシ).PropertyChanged + =(s、a)=> propertyName = a.PropertyName;
  8. proxy.Name = "aaaa" ;
  9. Assert.AreEqual( "Name" 、propertyName);
  10. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


実用化



このプロキシを使用して問題を解決してみましょう。これについては、記事の冒頭で説明します。 単純化するために、領域の小さなツリーを生成し、ユーザーに必要なものを選択させ、選択したもののリストを右側に表示させます。 実際には、ウィンドウコードは次のようになります





  1. パブリック MainWindow()
  2. {
  3. InitializeComponent();
  4. DataContext = this ;
  5. Items = new [] {DynamicProxy.Create(CreateRegions()。First())。AddProperty < bool >( "IsSelected" )};
  6. }
  7. IEnumerable <Region> GetSelectedItems( IEnumerable <dynamic>アイテム)
  8. {
  9. return items.Where(x => x.IsSelected).Concat(items.SelectMany(x => GetSelectedItems(( IEnumerable <dynamic>)x.SubRegions)))。Select(x =>(Region)x);
  10. }
  11. private void ButtonClick( オブジェクト送信者、RoutedEventArgs e)
  12. {
  13. var res = GetSelectedItems(アイテム).Take(10).ToList();
  14. SelectedItems = res;
  15. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


そして彼にxaml





  1. < Window x:Class = "WpfApplication1.MainWindow"
  2. xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication1 = "clr-namespace:WpfApplication1"
  4. タイトル = "MainWindow" 高さ = "350" = "525" >
  5. < Window.Resources >
  6. < DataTemplate DataType = "{x:Type WpfApplication1:Region}" >
  7. < WrapPanel >
  8. < TextBlock Text = "{Binding Path = Name、StringFormat = '{} {0}、'}" />
  9. < TextBlock Text = "{Binding Path = Index}" />
  10. </ WrapPanel >
  11. </ DataTemplate >
  12. </ Window.Resources >
  13. < グリッド >
  14. < Grid.ColumnDefinitions >
  15. < ColumnDefinition Width = "*" />
  16. < ColumnDefinition Width = "auto" />
  17. < ColumnDefinition Width = "*" />
  18. </ Grid.ColumnDefinitions >
  19. < TreeView グリッド = "0" ItemsSource = "{Binding Items}" BorderThickness = "0" >
  20. < TreeView.ItemTemplate >
  21. < HierarchicalDataTemplate ItemsSource = "{Binding SubRegions}" >
  22. < CheckBox IsChecked = "{Binding IsSelected、Mode = TwoWay}" Content = "{Binding Value}" />
  23. </ HierarchicalDataTemplate >
  24. </ TreeView.ItemTemplate >
  25. </ TreeView >
  26. < ボタンの 内容 =「選択を表示」 VerticalAlignment =「中央」 グリッド = "1" クリック = "ButtonClick" />
  27. < ListBox グリッド = "2" ItemsSource = "{Binding SelectedItems}" BorderThickness = "0" />
  28. </ グリッド >
  29. </ ウィンドウ >
*このソースコードは、 ソースコードハイライターで強調表示されました。




アプリケーションのスクリーンショット



画像



参照資料



  1. プロジェクトソース
  2. コンパイル済みバイナリ
  3. ダイナミックオブジェクト
  4. MVVM
  5. 流interfaceなインターフェイス


ご清聴ありがとうございました。




All Articles