Skip to content

Instantly share code, notes, and snippets.

@Y-Koji
Last active February 8, 2019 12:59
Show Gist options
  • Save Y-Koji/362bd03b5bf726c51e928f63492fa583 to your computer and use it in GitHub Desktop.
Save Y-Koji/362bd03b5bf726c51e928f63492fa583 to your computer and use it in GitHub Desktop.
黒魔術でINotifyPropertyChanged自動実装
<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>
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;
}
}
}
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