前提・実現したいこと
INotifyPropertyChangedの実装をなるべく短く書きたい(速度は度外視で)ので
次のようなViewModelBaseクラスを作成しました。
csharp
1public abstract class ViewModelBase : INotifyPropertyChanged 2{ 3 4 public event PropertyChangedEventHandler PropertyChanged; 5 6 protected virtual void RaisePropertyChanged(string propertyName) 7 { 8 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 9 } 10 11 protected virtual void RaisePropertyChanged(params string[] propertyNames) 12 { 13 foreach (var propertyName in propertyNames) RaisePropertyChanged(propertyName); 14 } 15 16 private Dictionary<string, object> PropertyDictionary = new Dictionary<string, object>(); 17 18 protected virtual void Setter(object value, [System.Runtime.CompilerServices.CallerMemberName] string PropertyName = "") 19 { 20 if (PropertyDictionary.ContainsKey(PropertyName)) 21 { 22 if (PropertyDictionary[PropertyName] == value) return; 23 PropertyDictionary[PropertyName] = value; 24 } 25 else 26 { 27 PropertyDictionary.Add(PropertyName, value); 28 } 29 RaisePropertyChanged(PropertyName); 30 } 31 32 protected virtual object Getter(string PropertyName) 33 { 34 return PropertyDictionary.TryGet(PropertyName); 35 } 36 37 protected virtual T Getter<T>([System.Runtime.CompilerServices.CallerMemberName] string PropertyName = "") 38 { 39 var ret = Getter(PropertyName); 40 if (ret == null) return default(T); 41 return (T)ret; 42 } 43} 44 45文字数制限で省略(編集履歴参照)
プロパティ名をCallerMemberNameでもらって、Dictionary<string, object>にすべての値を入れる方式です。
取得と設定にはGetterとSetterを使用して、次のような書き方が出来ます。
csharp
1public class Sample : ViewModelBase 2{ 3 public bool IsExpanded { get => Getter<bool>(); set => Setter(value); } 4 public bool? IsChecked { get => Getter<bool?>(); set => Setter(value); } 5 public string Text { get => Getter<string>(); set => Setter(value); } 6 public object Data { get => Getter<object>(); set => Setter(value); } 7 public Sample Parent { get => Getter<Sample>(); set => Setter(value); } 8 public ObservableCollection<Sample> Children { get => Getter<ObservableCollection<Sample>>(); set => Setter(value); } 9}
###質問
基本的に使用する際は
csharp
1public 型 名前 { get => Getter<型>(); set => Setter(value); }
これだけ書けばよいので短くて気に入っています。
ただ、同じ型名を2度書かないといけないため、ObservableCollectionだったりすると
かなり長くなってしまうのが気に入らないので、何か別の方法で、さらに短くできないかという質問です。
具体的には2度書いている型を1度にできないのか?returnでの型推論は効かせる方法はないのか?です。
###追記:仮想プロパティをオーバーライドするクラス動的生成
Zuishinさんの提案を参考に下記のように書き換えました。
csharp
1public abstract class ViewModelBase : INotifyPropertyChanged 2{ 3 4 public event PropertyChangedEventHandler PropertyChanged; 5 6 protected virtual void RaisePropertyChanged(string propertyName) 7 { 8 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 9 } 10 11 //ref:https://qiita.com/Zuishin/items/0b7080e6f7e277d9394b 12 //ref:http://www.gutgames.com/post/Overridding-a-Property-With-ReflectionEmit.aspx 13 private static Dictionary<Type, Type> typeDictionary = new Dictionary<Type, Type>(); 14 public static T Create<T>(params object[] parameters) where T : ViewModelBase 15 { 16 if (!typeDictionary.TryGetValue(typeof(T), out Type type)) 17 { 18 var name = "ViewModel_" + Guid.NewGuid().ToString("N"); 19 var assemblyName = new AssemblyName(name); 20 var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( 21 assemblyName, 22 AssemblyBuilderAccess.RunAndCollect); 23 var moduleBuilder = assemblyBuilder.DefineDynamicModule(name); 24 var typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class, typeof(T)); 25 //プロパティはvirtualではなくアクセサがvirtualになる。 26 var virtualProperties = typeof(T).GetProperties().Where(p => p.GetAccessors().Any(a => a.IsVirtual)); 27 foreach (var property in virtualProperties) 28 { 29 /* 30 * プロパティのオーバーライドするときは、Getter/Setterだけオーバーライドしないといけないらしい 31 * DefinePropertyでプロパティまで作ってしまうと、新クラスとベースクラスに二つのプロパティが生まれて 32 * 正しく動作しなくなってしまった 33 */ 34 35 //新しいプロパティ 36 //var propertyBuilder = typeBuilder.DefineProperty( 37 // property.Name, 38 // PropertyAttributes.None, 39 // property.PropertyType, 40 // new Type[] { property.PropertyType } 41 // ); 42 //フィールド 43 //バッキングフィールドは直接触れなかった 44 //var field = typeof(T).GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 45 //なので新しいフィールド作成 46 var field = typeBuilder.DefineField($"field_{property.PropertyType}", property.PropertyType, FieldAttributes.Private); 47 48 var accessorAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual; 49 50 //Getter 51 var getMethod = typeBuilder.DefineMethod($"get_{property.Name}", accessorAttr, property.PropertyType, Type.EmptyTypes); 52 var getIL = getMethod.GetILGenerator(); 53 getIL.Emit(OpCodes.Ldarg_0); 54 getIL.Emit(OpCodes.Ldfld, field); 55 getIL.Emit(OpCodes.Ret); 56 57 //propertyBuilder.SetGetMethod(getMethod); 58 59 //Setter 60 var setMethod = typeBuilder.DefineMethod($"set_{property.Name}", accessorAttr, null, new Type[] { property.PropertyType }); 61 var setIL = setMethod.GetILGenerator(); 62 setIL.Emit(OpCodes.Ldarg_0); 63 setIL.Emit(OpCodes.Ldarg_1); 64 setIL.Emit(OpCodes.Stfld, field); 65 setIL.Emit(OpCodes.Ldarg_0); 66 setIL.Emit(OpCodes.Ldstr, property.Name); 67 var meth = typeof(T).GetMethod("RaisePropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Type.DefaultBinder, new Type[] { typeof(string) }, null); 68 setIL.Emit(OpCodes.Callvirt, meth); 69 setIL.Emit(OpCodes.Ret); 70 71 //propertyBuilder.SetSetMethod(setMethod); 72 } 73 type = typeBuilder.CreateType(); 74 typeDictionary[typeof(T)] = type; 75 } 76 return (T)Activator.CreateInstance(type, parameters); 77 } 78}
csharp
1public class CheckTreeSource : ViewModelBase 2{ 3 public virtual bool IsExpanded { get; set; } 4 public virtual bool? IsChecked { get; set; } 5 public virtual string Text { get; set; } 6 public virtual object Data { get; set; } 7 public virtual CheckTreeSource Parent { get; set; } 8 public virtual ObservableCollection<CheckTreeSource> Children { get; set; } 9}
使用方法は
csharp
1var item = ViewModelBase.Create<CheckTreeSource>();
動作確認に使用したコードは
XAML
1<Window x:Class="ViewModelSample.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:ViewModelSample" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="450" Width="800"> 9 <DockPanel> 10 <Button DockPanel.Dock="Top" Content="CheckAllItems" Click="Button_Click"/> 11 <TreeView Name="CheckTreeView"> 12 <TreeView.Resources> 13 <Style TargetType="TreeViewItem"> 14 <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/> 15 </Style> 16 </TreeView.Resources> 17 <TreeView.ItemTemplate> 18 <HierarchicalDataTemplate DataType="local:CheckTreeSource" ItemsSource="{Binding Children}"> 19 <CheckBox Margin="1" IsChecked="{Binding IsChecked}"> 20 <TextBlock Text="{Binding Text}"/> 21 </CheckBox> 22 </HierarchicalDataTemplate> 23 </TreeView.ItemTemplate> 24 </TreeView> 25 </DockPanel> 26</Window> 27
csharp
1public partial class MainWindow : Window 2{ 3 private ObservableCollection<CheckTreeSource> checkItems = new ObservableCollection<CheckTreeSource>(); 4 public MainWindow() 5 { 6 InitializeComponent(); 7 var random = new Random(); 8 9 10 foreach (var i in Enumerable.Range(0, 20)) 11 { 12 var item = ViewModelBase.Create<CheckTreeSource>(); 13 item.Text = $"Parent{i}"; 14 item.IsChecked = random.Next(2) == 0; 15 item.IsExpanded = true; 16 checkItems.Add(item); 17 } 18 19 foreach (var item in checkItems) 20 { 21 item.Children = new ObservableCollection<CheckTreeSource>(); 22 foreach (var i in Enumerable.Range(0, 3)) 23 { 24 var childitem = ViewModelBase.Create<CheckTreeSource>(); 25 childitem.Parent = item; 26 childitem.Text = $"Child{i}"; 27 childitem.IsChecked = item.IsChecked; 28 item.Children.Add(childitem); 29 } 30 } 31 32 CheckTreeView.ItemsSource = checkItems; 33 } 34 35 private void Button_Click(object sender, RoutedEventArgs e) 36 { 37 foreach(var item in checkItems) 38 { 39 item.IsChecked = true; 40 foreach(var childitem in item.Children) 41 { 42 childitem.IsChecked = true; 43 } 44 } 45 } 46}
初めてILに触ったのでとても勉強になりました。
プロパティのオーバーライドもgetter/setterだけオーバーライドでいいと思わず、新発見でした。
値を入れてたDictionaryも無くなってとってもいい感じです。

回答3件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/22 09:23
2018/03/22 15:07
2018/03/23 01:20
2018/03/23 05:07
2018/03/23 05:10
2018/03/25 13:01