質問編集履歴

1

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

2018/03/23 05:04

投稿

sh_akira
sh_akira

スコア380

test CHANGED
File without changes
test CHANGED
@@ -98,19 +98,35 @@
98
98
 
99
99
 
100
100
 
101
+ 文字数制限で省略(編集履歴参照)
102
+
103
+ ```
104
+
105
+
106
+
107
+ プロパティ名をCallerMemberNameでもらって、Dictionary<string, object>にすべての値を入れる方式です。
108
+
109
+ 取得と設定にはGetterとSetterを使用して、次のような書き方が出来ます。
110
+
111
+
112
+
113
+ ```csharp
114
+
101
- public static class IDictionaryExtensions
115
+ public class Sample : ViewModelBase
102
116
 
103
117
  {
104
118
 
119
+ public bool IsExpanded { get => Getter<bool>(); set => Setter(value); }
120
+
121
+ public bool? IsChecked { get => Getter<bool?>(); set => Setter(value); }
122
+
105
- public static TValue TryGet<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
123
+ public string Text { get => Getter<string>(); set => Setter(value); }
106
-
124
+
107
- {
125
+ public object Data { get => Getter<object>(); set => Setter(value); }
108
-
126
+
109
- if (source.ContainsKey(key)) return source[key];
127
+ public Sample Parent { get => Getter<Sample>(); set => Setter(value); }
110
-
128
+
111
- return default(TValue);
129
+ public ObservableCollection<Sample> Children { get => Getter<ObservableCollection<Sample>>(); set => Setter(value); }
112
-
113
- }
114
130
 
115
131
  }
116
132
 
@@ -118,29 +134,189 @@
118
134
 
119
135
 
120
136
 
121
- プロパティ名をCallerMemberNameでもらって、Dictionary<string, object>にすべての値を入れる方式です。
137
+ ###質問
122
-
138
+
123
- 取得と設定はGetterとSetterを使用して、次のような書き方が出来ま
139
+ 基本的に使用する際は
124
-
125
-
126
-
140
+
127
- ```csharp
141
+ ```csharp
142
+
128
-
143
+ public 型 名前 { get => Getter<型>(); set => Setter(value); }
144
+
145
+ ```
146
+
147
+ これだけ書けばよいので短くて気に入っています。
148
+
149
+ ただ、同じ型名を2度書かないといけないため、ObservableCollectionだったりすると
150
+
151
+ かなり長くなってしまうのが気に入らないので、何か別の方法で、さらに短くできないかという質問です。
152
+
153
+ 具体的には2度書いている型を1度にできないのか?returnでの型推論は効かせる方法はないのか?です。
154
+
155
+
156
+
157
+
158
+
159
+ ###追記:仮想プロパティをオーバーライドするクラス動的生成
160
+
161
+
162
+
163
+ Zuishinさんの提案を参考に下記のように書き換えました。
164
+
165
+ ```csharp
166
+
129
- public class Sample : ViewModelBase
167
+ public abstract class ViewModelBase : INotifyPropertyChanged
130
168
 
131
169
  {
132
170
 
133
- public bool IsExpanded { get => Getter<bool>(); set => Setter(value); }
171
+
134
-
172
+
135
- public bool? IsChecked { get => Getter<bool?>(); set => Setter(value); }
173
+ public event PropertyChangedEventHandler PropertyChanged;
136
-
174
+
175
+
176
+
137
- public string Text { get => Getter<string>(); set => Setter(value); }
177
+ protected virtual void RaisePropertyChanged(string propertyName)
178
+
138
-
179
+ {
180
+
181
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
182
+
183
+ }
184
+
185
+
186
+
187
+ //ref:https://qiita.com/Zuishin/items/0b7080e6f7e277d9394b
188
+
189
+ //ref:http://www.gutgames.com/post/Overridding-a-Property-With-ReflectionEmit.aspx
190
+
191
+ private static Dictionary<Type, Type> typeDictionary = new Dictionary<Type, Type>();
192
+
139
- public object Data { get => Getter<object>(); set => Setter(value); }
193
+ public static T Create<T>(params object[] parameters) where T : ViewModelBase
194
+
140
-
195
+ {
196
+
197
+ if (!typeDictionary.TryGetValue(typeof(T), out Type type))
198
+
199
+ {
200
+
201
+ var name = "ViewModel_" + Guid.NewGuid().ToString("N");
202
+
203
+ var assemblyName = new AssemblyName(name);
204
+
205
+ var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
206
+
207
+ assemblyName,
208
+
209
+ AssemblyBuilderAccess.RunAndCollect);
210
+
211
+ var moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
212
+
213
+ var typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class, typeof(T));
214
+
215
+ //プロパティはvirtualではなくアクセサがvirtualになる。
216
+
141
- public Sample Parent { get => Getter<Sample>(); set => Setter(value); }
217
+ var virtualProperties = typeof(T).GetProperties().Where(p => p.GetAccessors().Any(a => a.IsVirtual));
218
+
142
-
219
+ foreach (var property in virtualProperties)
220
+
221
+ {
222
+
223
+ /*
224
+
225
+ * プロパティのオーバーライドするときは、Getter/Setterだけオーバーライドしないといけないらしい
226
+
227
+ * DefinePropertyでプロパティまで作ってしまうと、新クラスとベースクラスに二つのプロパティが生まれて
228
+
229
+ * 正しく動作しなくなってしまった
230
+
231
+ */
232
+
233
+
234
+
235
+ //新しいプロパティ
236
+
237
+ //var propertyBuilder = typeBuilder.DefineProperty(
238
+
239
+ // property.Name,
240
+
241
+ // PropertyAttributes.None,
242
+
243
+ // property.PropertyType,
244
+
245
+ // new Type[] { property.PropertyType }
246
+
247
+ // );
248
+
249
+ //フィールド
250
+
251
+ //バッキングフィールドは直接触れなかった
252
+
253
+ //var field = typeof(T).GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
254
+
255
+ //なので新しいフィールド作成
256
+
257
+ var field = typeBuilder.DefineField($"field_{property.PropertyType}", property.PropertyType, FieldAttributes.Private);
258
+
259
+
260
+
143
- public ObservableCollection<Sample> Children { get => Getter<ObservableCollection<Sample>>(); set => Setter(value); }
261
+ var accessorAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
262
+
263
+
264
+
265
+ //Getter
266
+
267
+ var getMethod = typeBuilder.DefineMethod($"get_{property.Name}", accessorAttr, property.PropertyType, Type.EmptyTypes);
268
+
269
+ var getIL = getMethod.GetILGenerator();
270
+
271
+ getIL.Emit(OpCodes.Ldarg_0);
272
+
273
+ getIL.Emit(OpCodes.Ldfld, field);
274
+
275
+ getIL.Emit(OpCodes.Ret);
276
+
277
+
278
+
279
+ //propertyBuilder.SetGetMethod(getMethod);
280
+
281
+
282
+
283
+ //Setter
284
+
285
+ var setMethod = typeBuilder.DefineMethod($"set_{property.Name}", accessorAttr, null, new Type[] { property.PropertyType });
286
+
287
+ var setIL = setMethod.GetILGenerator();
288
+
289
+ setIL.Emit(OpCodes.Ldarg_0);
290
+
291
+ setIL.Emit(OpCodes.Ldarg_1);
292
+
293
+ setIL.Emit(OpCodes.Stfld, field);
294
+
295
+ setIL.Emit(OpCodes.Ldarg_0);
296
+
297
+ setIL.Emit(OpCodes.Ldstr, property.Name);
298
+
299
+ var meth = typeof(T).GetMethod("RaisePropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Type.DefaultBinder, new Type[] { typeof(string) }, null);
300
+
301
+ setIL.Emit(OpCodes.Callvirt, meth);
302
+
303
+ setIL.Emit(OpCodes.Ret);
304
+
305
+
306
+
307
+ //propertyBuilder.SetSetMethod(setMethod);
308
+
309
+ }
310
+
311
+ type = typeBuilder.CreateType();
312
+
313
+ typeDictionary[typeof(T)] = type;
314
+
315
+ }
316
+
317
+ return (T)Activator.CreateInstance(type, parameters);
318
+
319
+ }
144
320
 
145
321
  }
146
322
 
@@ -148,26 +324,196 @@
148
324
 
149
325
 
150
326
 
151
- ###質問
152
-
153
- 基本的に使用する際は
154
-
155
- ```csharp
156
-
157
- public 名前 { get => Getter<型>(); set => Setter(value); }
158
-
159
- ```
160
-
161
- これだけ書けばよいので短くて気に入っています。
162
-
163
- ただ、同じ型名を2度書かないといけないため、ObservableCollectionだったりすると
164
-
165
- かなり長くなってしまうのが気に入らないので、何か別の方法で、さらに短くできないかという質問です。
166
-
167
- 具体的には2度書いている型を1度にできないのか?returnでの型推論は効かせる方法はないのか?です。
168
-
169
- そもそも現状のコードに何か問題がある場合はご指摘お願いします。
170
-
171
-
172
-
173
- WPFとXamarin.Formsで使用しています。C#のバージョンの指定はありません。
327
+ ```csharp
328
+
329
+ public class CheckTreeSource : ViewModelBase
330
+
331
+ {
332
+
333
+ public virtual bool IsExpanded { get; set; }
334
+
335
+ public virtual bool? IsChecked { get; set; }
336
+
337
+ public virtual string Text { get; set; }
338
+
339
+ public virtual object Data { get; set; }
340
+
341
+ public virtual CheckTreeSource Parent { get; set; }
342
+
343
+ public virtual ObservableCollection<CheckTreeSource> Children { get; set; }
344
+
345
+ }
346
+
347
+ ```
348
+
349
+
350
+
351
+ 使用方法は
352
+
353
+ ```csharp
354
+
355
+ var item = ViewModelBase.Create<CheckTreeSource>();
356
+
357
+ ```
358
+
359
+ 動作確認に使用したコードは
360
+
361
+ ```XAML
362
+
363
+ <Window x:Class="ViewModelSample.MainWindow"
364
+
365
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
366
+
367
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
368
+
369
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
370
+
371
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
372
+
373
+ xmlns:local="clr-namespace:ViewModelSample"
374
+
375
+ mc:Ignorable="d"
376
+
377
+ Title="MainWindow" Height="450" Width="800">
378
+
379
+ <DockPanel>
380
+
381
+ <Button DockPanel.Dock="Top" Content="CheckAllItems" Click="Button_Click"/>
382
+
383
+ <TreeView Name="CheckTreeView">
384
+
385
+ <TreeView.Resources>
386
+
387
+ <Style TargetType="TreeViewItem">
388
+
389
+ <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
390
+
391
+ </Style>
392
+
393
+ </TreeView.Resources>
394
+
395
+ <TreeView.ItemTemplate>
396
+
397
+ <HierarchicalDataTemplate DataType="local:CheckTreeSource" ItemsSource="{Binding Children}">
398
+
399
+ <CheckBox Margin="1" IsChecked="{Binding IsChecked}">
400
+
401
+ <TextBlock Text="{Binding Text}"/>
402
+
403
+ </CheckBox>
404
+
405
+ </HierarchicalDataTemplate>
406
+
407
+ </TreeView.ItemTemplate>
408
+
409
+ </TreeView>
410
+
411
+ </DockPanel>
412
+
413
+ </Window>
414
+
415
+
416
+
417
+ ```
418
+
419
+ ```csharp
420
+
421
+ public partial class MainWindow : Window
422
+
423
+ {
424
+
425
+ private ObservableCollection<CheckTreeSource> checkItems = new ObservableCollection<CheckTreeSource>();
426
+
427
+ public MainWindow()
428
+
429
+ {
430
+
431
+ InitializeComponent();
432
+
433
+ var random = new Random();
434
+
435
+
436
+
437
+
438
+
439
+ foreach (var i in Enumerable.Range(0, 20))
440
+
441
+ {
442
+
443
+ var item = ViewModelBase.Create<CheckTreeSource>();
444
+
445
+ item.Text = $"Parent{i}";
446
+
447
+ item.IsChecked = random.Next(2) == 0;
448
+
449
+ item.IsExpanded = true;
450
+
451
+ checkItems.Add(item);
452
+
453
+ }
454
+
455
+
456
+
457
+ foreach (var item in checkItems)
458
+
459
+ {
460
+
461
+ item.Children = new ObservableCollection<CheckTreeSource>();
462
+
463
+ foreach (var i in Enumerable.Range(0, 3))
464
+
465
+ {
466
+
467
+ var childitem = ViewModelBase.Create<CheckTreeSource>();
468
+
469
+ childitem.Parent = item;
470
+
471
+ childitem.Text = $"Child{i}";
472
+
473
+ childitem.IsChecked = item.IsChecked;
474
+
475
+ item.Children.Add(childitem);
476
+
477
+ }
478
+
479
+ }
480
+
481
+
482
+
483
+ CheckTreeView.ItemsSource = checkItems;
484
+
485
+ }
486
+
487
+
488
+
489
+ private void Button_Click(object sender, RoutedEventArgs e)
490
+
491
+ {
492
+
493
+ foreach(var item in checkItems)
494
+
495
+ {
496
+
497
+ item.IsChecked = true;
498
+
499
+ foreach(var childitem in item.Children)
500
+
501
+ {
502
+
503
+ childitem.IsChecked = true;
504
+
505
+ }
506
+
507
+ }
508
+
509
+ }
510
+
511
+ }
512
+
513
+ ```
514
+
515
+ 初めてILに触ったのでとても勉強になりました。
516
+
517
+ プロパティのオーバーライドもgetter/setterだけオーバーライドでいいと思わず、新発見でした。
518
+
519
+ 値を入れてたDictionaryも無くなってとってもいい感じです。