teratail header banner
teratail header banner
質問するログイン新規登録

質問編集履歴

1

仮想プロパティをオーバーライドする方式追記

2018/03/23 05:04

投稿

sh_akira
sh_akira

スコア380

title CHANGED
File without changes
body CHANGED
@@ -48,14 +48,7 @@
48
48
  }
49
49
  }
50
50
 
51
- public static class IDictionaryExtensions
52
- {
53
- public static TValue TryGet<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
54
- {
55
- if (source.ContainsKey(key)) return source[key];
56
- return default(TValue);
51
+ 文字数制限で省略(編集履歴参照)
57
- }
58
- }
59
52
  ```
60
53
 
61
54
  プロパティ名をCallerMemberNameでもらって、Dictionary<string, object>にすべての値を入れる方式です。
@@ -82,6 +75,186 @@
82
75
  ただ、同じ型名を2度書かないといけないため、ObservableCollectionだったりすると
83
76
  かなり長くなってしまうのが気に入らないので、何か別の方法で、さらに短くできないかという質問です。
84
77
  具体的には2度書いている型を1度にできないのか?returnでの型推論は効かせる方法はないのか?です。
85
- そもそも現状のコードに何か問題がある場合はご指摘お願いします。
86
78
 
79
+
80
+ ###追記:仮想プロパティをオーバーライドするクラス動的生成
81
+
82
+ Zuishinさんの提案を参考に下記のように書き換えました。
83
+ ```csharp
84
+ public abstract class ViewModelBase : INotifyPropertyChanged
85
+ {
86
+
87
+ public event PropertyChangedEventHandler PropertyChanged;
88
+
89
+ protected virtual void RaisePropertyChanged(string propertyName)
90
+ {
91
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
92
+ }
93
+
94
+ //ref:https://qiita.com/Zuishin/items/0b7080e6f7e277d9394b
95
+ //ref:http://www.gutgames.com/post/Overridding-a-Property-With-ReflectionEmit.aspx
96
+ private static Dictionary<Type, Type> typeDictionary = new Dictionary<Type, Type>();
97
+ public static T Create<T>(params object[] parameters) where T : ViewModelBase
98
+ {
99
+ if (!typeDictionary.TryGetValue(typeof(T), out Type type))
100
+ {
101
+ var name = "ViewModel_" + Guid.NewGuid().ToString("N");
87
- WPFとXamarin.Formsで使用しています。C#のバージョンの指定はありません。
102
+ var assemblyName = new AssemblyName(name);
103
+ var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
104
+ assemblyName,
105
+ AssemblyBuilderAccess.RunAndCollect);
106
+ var moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
107
+ var typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class, typeof(T));
108
+ //プロパティはvirtualではなくアクセサがvirtualになる。
109
+ var virtualProperties = typeof(T).GetProperties().Where(p => p.GetAccessors().Any(a => a.IsVirtual));
110
+ foreach (var property in virtualProperties)
111
+ {
112
+ /*
113
+ * プロパティのオーバーライドするときは、Getter/Setterだけオーバーライドしないといけないらしい
114
+ * DefinePropertyでプロパティまで作ってしまうと、新クラスとベースクラスに二つのプロパティが生まれて
115
+ * 正しく動作しなくなってしまった
116
+ */
117
+
118
+ //新しいプロパティ
119
+ //var propertyBuilder = typeBuilder.DefineProperty(
120
+ // property.Name,
121
+ // PropertyAttributes.None,
122
+ // property.PropertyType,
123
+ // new Type[] { property.PropertyType }
124
+ // );
125
+ //フィールド
126
+ //バッキングフィールドは直接触れなかった
127
+ //var field = typeof(T).GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
128
+ //なので新しいフィールド作成
129
+ var field = typeBuilder.DefineField($"field_{property.PropertyType}", property.PropertyType, FieldAttributes.Private);
130
+
131
+ var accessorAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
132
+
133
+ //Getter
134
+ var getMethod = typeBuilder.DefineMethod($"get_{property.Name}", accessorAttr, property.PropertyType, Type.EmptyTypes);
135
+ var getIL = getMethod.GetILGenerator();
136
+ getIL.Emit(OpCodes.Ldarg_0);
137
+ getIL.Emit(OpCodes.Ldfld, field);
138
+ getIL.Emit(OpCodes.Ret);
139
+
140
+ //propertyBuilder.SetGetMethod(getMethod);
141
+
142
+ //Setter
143
+ var setMethod = typeBuilder.DefineMethod($"set_{property.Name}", accessorAttr, null, new Type[] { property.PropertyType });
144
+ var setIL = setMethod.GetILGenerator();
145
+ setIL.Emit(OpCodes.Ldarg_0);
146
+ setIL.Emit(OpCodes.Ldarg_1);
147
+ setIL.Emit(OpCodes.Stfld, field);
148
+ setIL.Emit(OpCodes.Ldarg_0);
149
+ setIL.Emit(OpCodes.Ldstr, property.Name);
150
+ var meth = typeof(T).GetMethod("RaisePropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Type.DefaultBinder, new Type[] { typeof(string) }, null);
151
+ setIL.Emit(OpCodes.Callvirt, meth);
152
+ setIL.Emit(OpCodes.Ret);
153
+
154
+ //propertyBuilder.SetSetMethod(setMethod);
155
+ }
156
+ type = typeBuilder.CreateType();
157
+ typeDictionary[typeof(T)] = type;
158
+ }
159
+ return (T)Activator.CreateInstance(type, parameters);
160
+ }
161
+ }
162
+ ```
163
+
164
+ ```csharp
165
+ public class CheckTreeSource : ViewModelBase
166
+ {
167
+ public virtual bool IsExpanded { get; set; }
168
+ public virtual bool? IsChecked { get; set; }
169
+ public virtual string Text { get; set; }
170
+ public virtual object Data { get; set; }
171
+ public virtual CheckTreeSource Parent { get; set; }
172
+ public virtual ObservableCollection<CheckTreeSource> Children { get; set; }
173
+ }
174
+ ```
175
+
176
+ 使用方法は
177
+ ```csharp
178
+ var item = ViewModelBase.Create<CheckTreeSource>();
179
+ ```
180
+ 動作確認に使用したコードは
181
+ ```XAML
182
+ <Window x:Class="ViewModelSample.MainWindow"
183
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
184
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
185
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
186
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
187
+ xmlns:local="clr-namespace:ViewModelSample"
188
+ mc:Ignorable="d"
189
+ Title="MainWindow" Height="450" Width="800">
190
+ <DockPanel>
191
+ <Button DockPanel.Dock="Top" Content="CheckAllItems" Click="Button_Click"/>
192
+ <TreeView Name="CheckTreeView">
193
+ <TreeView.Resources>
194
+ <Style TargetType="TreeViewItem">
195
+ <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
196
+ </Style>
197
+ </TreeView.Resources>
198
+ <TreeView.ItemTemplate>
199
+ <HierarchicalDataTemplate DataType="local:CheckTreeSource" ItemsSource="{Binding Children}">
200
+ <CheckBox Margin="1" IsChecked="{Binding IsChecked}">
201
+ <TextBlock Text="{Binding Text}"/>
202
+ </CheckBox>
203
+ </HierarchicalDataTemplate>
204
+ </TreeView.ItemTemplate>
205
+ </TreeView>
206
+ </DockPanel>
207
+ </Window>
208
+
209
+ ```
210
+ ```csharp
211
+ public partial class MainWindow : Window
212
+ {
213
+ private ObservableCollection<CheckTreeSource> checkItems = new ObservableCollection<CheckTreeSource>();
214
+ public MainWindow()
215
+ {
216
+ InitializeComponent();
217
+ var random = new Random();
218
+
219
+
220
+ foreach (var i in Enumerable.Range(0, 20))
221
+ {
222
+ var item = ViewModelBase.Create<CheckTreeSource>();
223
+ item.Text = $"Parent{i}";
224
+ item.IsChecked = random.Next(2) == 0;
225
+ item.IsExpanded = true;
226
+ checkItems.Add(item);
227
+ }
228
+
229
+ foreach (var item in checkItems)
230
+ {
231
+ item.Children = new ObservableCollection<CheckTreeSource>();
232
+ foreach (var i in Enumerable.Range(0, 3))
233
+ {
234
+ var childitem = ViewModelBase.Create<CheckTreeSource>();
235
+ childitem.Parent = item;
236
+ childitem.Text = $"Child{i}";
237
+ childitem.IsChecked = item.IsChecked;
238
+ item.Children.Add(childitem);
239
+ }
240
+ }
241
+
242
+ CheckTreeView.ItemsSource = checkItems;
243
+ }
244
+
245
+ private void Button_Click(object sender, RoutedEventArgs e)
246
+ {
247
+ foreach(var item in checkItems)
248
+ {
249
+ item.IsChecked = true;
250
+ foreach(var childitem in item.Children)
251
+ {
252
+ childitem.IsChecked = true;
253
+ }
254
+ }
255
+ }
256
+ }
257
+ ```
258
+ 初めてILに触ったのでとても勉強になりました。
259
+ プロパティのオーバーライドもgetter/setterだけオーバーライドでいいと思わず、新発見でした。
260
+ 値を入れてたDictionaryも無くなってとってもいい感じです。