Last active
February 8, 2019 12:59
-
-
Save Y-Koji/362bd03b5bf726c51e928f63492fa583 to your computer and use it in GitHub Desktop.
黒魔術でINotifyPropertyChanged自動実装
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window x:Class="RexView.View.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
xmlns:vm="clr-namespace:ViewModel" | |
xmlns:mvvm="clr-namespace:MVVM" | |
xmlns:m="clr-namespace:Model" | |
mc:Ignorable="d" | |
Title="MainWindow" Height="450" Width="800"> | |
<Window.Resources> | |
<mvvm:NotifyPropertyConverter x:Key="NotifyObjectConverter" /> | |
</Window.Resources> | |
<Window.DataContext> | |
<Binding Mode="OneWay" | |
Source="{x:Type vm:MainViewModel}" | |
Converter="{StaticResource NotifyObjectConverter}" /> | |
</Window.DataContext> | |
<Grid> | |
</Grid> | |
</Window> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.ComponentModel; | |
using System.Linq; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
namespace MVVM | |
{ | |
// Referenced: | |
// https://teratail.com/questions/118565 | |
// https://grahammurray.wordpress.com/2010/04/13/dynamically-generating-types-to-implement-inotifypropertychanged/ | |
public static class NotifyProperty | |
{ | |
public static T Create<T>() | |
=> (T)Create(typeof(T), null); | |
public static T Create<T>(T baseInstance) | |
=> (T)Create(typeof(T), baseInstance); | |
public static INotifyPropertyChanged Create(Type type) | |
=> Create(type, null); | |
/// <summary>INotifyPropertyChangedインターフェースを自動実装してインスタンスを生成します</summary> | |
/// <param name="baseType">自動実装する対象の型</param> | |
/// <returns>INotifyPropertyChanged実装済みインスタンス</returns> | |
public static INotifyPropertyChanged Create(Type baseType, object baseInstance) | |
{ | |
var name = "NotifyObject_" + Guid.NewGuid().ToString("N"); | |
var asmName = new AssemblyName(name); | |
var domain = AppDomain.CurrentDomain; | |
var asmBuilder = domain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndCollect); | |
var moduleBuilder = asmBuilder.DefineDynamicModule("Module"); | |
var type = moduleBuilder.DefineType("Notify_" + baseType.Name, TypeAttributes.Public | TypeAttributes.Class, baseType); | |
type.AddInterfaceImplementation(typeof(INotifyPropertyChanged)); | |
var @event = type.DefineEvent(nameof(INotifyPropertyChanged.PropertyChanged), EventAttributes.None, typeof(PropertyChangedEventHandler)); | |
var field = type.DefineField( | |
nameof(INotifyPropertyChanged.PropertyChanged), | |
typeof(PropertyChangedEventHandler), | |
FieldAttributes.Private); | |
@event.SetAddOnMethod(CreateAddRemoveMethod(type, field, true)); | |
@event.SetRemoveOnMethod(CreateAddRemoveMethod(type, field, false)); | |
MethodBuilder raisePropertyChanged = CreateRaisePropertyChanged(type, field); | |
foreach (var prop in baseType.GetProperties()) | |
{ | |
var baseGetter = baseType.GetMethod($"get_{prop.Name}", (BindingFlags)0xFF); | |
if (baseGetter.IsVirtual) | |
{ | |
var getter = | |
type.DefineMethod( | |
"get_" + prop.Name, baseGetter.Attributes, | |
prop.PropertyType, Type.EmptyTypes); | |
var il = getter.GetILGenerator(); | |
// base.get_[PropertyName] | |
il.Emit(OpCodes.Nop); | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Call, baseGetter); | |
il.Emit(OpCodes.Ret); | |
type.DefineMethodOverride(getter, baseGetter); | |
} | |
var baseSetter = baseType.GetMethod($"set_{prop.Name}", (BindingFlags)0xFF); | |
if (baseSetter.IsVirtual) | |
{ | |
if (null != baseSetter) | |
{ | |
var setter = | |
type.DefineMethod( | |
"set_" + prop.Name, baseSetter.Attributes, | |
null, new Type[] { prop.PropertyType }); | |
var il = setter.GetILGenerator(); | |
// base.set_[PropertyName] | |
il.Emit(OpCodes.Nop); | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldarg_1); | |
il.Emit(OpCodes.Call, baseSetter); | |
// RaisePropertyChanged | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldstr, prop.Name); | |
il.Emit(OpCodes.Callvirt, raisePropertyChanged); | |
il.Emit(OpCodes.Ret); | |
type.DefineMethodOverride(setter, baseSetter); | |
} | |
} | |
} | |
foreach (var attr in baseType.GetCustomAttributes()) | |
{ | |
//var ctor = attr.GetType().GetConstructor(new Type[] { }); | |
//type.SetCustomAttribute(new CustomAttributeBuilder(ctor, new object[] { })); | |
} | |
object instance = Activator.CreateInstance(type.CreateType()); | |
if (null != baseInstance && instance.GetType().BaseType == baseInstance.GetType()) | |
{ | |
CopyFields(baseInstance, instance); | |
} | |
return instance as INotifyPropertyChanged; | |
} | |
private static void CopyFields(object src, object dst) | |
{ | |
if (null != src && null != dst) | |
{ | |
foreach (var field in src.GetType().GetFields((BindingFlags)0xFF)) | |
{ | |
var notifyAttributes = field.FieldType.CustomAttributes.OfType<NotifyAttribute>(); | |
if (0 < notifyAttributes.Count()) | |
{ | |
object value = field.GetValue(src); | |
object notifyPropertyObject = Create(field.FieldType, value); | |
field.SetValue(dst, notifyPropertyObject); | |
} | |
else | |
{ | |
field.SetValue(dst, field.GetValue(src)); | |
} | |
} | |
} | |
} | |
private static MethodBuilder CreateAddRemoveMethod( | |
TypeBuilder typeBuilder, | |
FieldBuilder eventField, | |
bool isAdd) | |
{ | |
string prefix = "remove_"; | |
string delegateAction = "Remove"; | |
if (isAdd) | |
{ | |
prefix = "add_"; | |
delegateAction = "Combine"; | |
} | |
MethodBuilder addremoveMethod = | |
typeBuilder.DefineMethod(prefix + "PropertyChanged", | |
MethodAttributes.Public | | |
MethodAttributes.SpecialName | | |
MethodAttributes.NewSlot | | |
MethodAttributes.HideBySig | | |
MethodAttributes.Virtual | | |
MethodAttributes.Final, | |
null, | |
new[] { typeof(PropertyChangedEventHandler) }); | |
MethodImplAttributes eventMethodFlags = | |
MethodImplAttributes.Managed | | |
MethodImplAttributes.Synchronized; | |
addremoveMethod.SetImplementationFlags(eventMethodFlags); | |
ILGenerator ilGen = addremoveMethod.GetILGenerator(); | |
// PropertyChanged += value; // PropertyChanged -= value; | |
ilGen.Emit(OpCodes.Ldarg_0); | |
ilGen.Emit(OpCodes.Ldarg_0); | |
ilGen.Emit(OpCodes.Ldfld, eventField); | |
ilGen.Emit(OpCodes.Ldarg_1); | |
ilGen.EmitCall( | |
OpCodes.Call, | |
typeof(Delegate).GetMethod(delegateAction, new[] { typeof(Delegate), typeof(Delegate) }), | |
null); | |
ilGen.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler)); | |
ilGen.Emit(OpCodes.Stfld, eventField); | |
ilGen.Emit(OpCodes.Ret); | |
MethodInfo intAddRemoveMethod = | |
typeof(INotifyPropertyChanged) | |
.GetMethod(prefix + "PropertyChanged"); | |
typeBuilder.DefineMethodOverride(addremoveMethod, intAddRemoveMethod); | |
return addremoveMethod; | |
} | |
private static MethodBuilder CreateRaisePropertyChanged( | |
TypeBuilder typeBuilder, | |
FieldBuilder eventField) | |
{ | |
MethodBuilder raisePropertyChangedBuilder = | |
typeBuilder.DefineMethod( | |
"RaisePropertyChanged", | |
MethodAttributes.Family | MethodAttributes.Virtual, | |
null, new Type[] { typeof(string) }); | |
ILGenerator il = raisePropertyChangedBuilder.GetILGenerator(); | |
Label labelExit = il.DefineLabel(); | |
// if (PropertyChanged == null) | |
// { | |
// return; | |
// } | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldfld, eventField); | |
il.Emit(OpCodes.Ldnull); | |
il.Emit(OpCodes.Ceq); | |
il.Emit(OpCodes.Brtrue, labelExit); | |
// this.PropertyChanged(this, | |
// new PropertyChangedEventArgs(propertyName)); | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldfld, eventField); | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldarg_1); | |
il.Emit(OpCodes.Newobj, | |
typeof(PropertyChangedEventArgs).GetConstructor(new[] { typeof(string) })); | |
il.EmitCall(OpCodes.Callvirt, | |
typeof(PropertyChangedEventHandler).GetMethod("Invoke"), null); | |
// return; | |
il.MarkLabel(labelExit); | |
il.Emit(OpCodes.Ret); | |
return raisePropertyChangedBuilder; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Globalization; | |
using System.Windows.Data; | |
namespace MVVM | |
{ | |
public class NotifyPropertyConverter : IValueConverter | |
{ | |
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
{ | |
if (value is Type type) | |
{ | |
return NotifyProperty.Create(type); | |
} | |
else | |
{ | |
throw new ArgumentException(); | |
} | |
} | |
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment