Mercurial > pub > bltoolkit
view Source/TypeBuilder/Builders/PropertyChangedBuilder.cs @ 4:f757da6161a1
!bug 100 + 2h fixed gregression
author | cin |
---|---|
date | Sun, 24 Aug 2014 17:57:42 +0400 |
parents | f990fcb411a9 |
children |
line wrap: on
line source
using System; using System.Reflection; using System.Reflection.Emit; using BLToolkit.Reflection; using BLToolkit.Reflection.Emit; namespace BLToolkit.TypeBuilder.Builders { public class PropertyChangedBuilder : AbstractTypeBuilderBase { public PropertyChangedBuilder() : this(Common.Configuration.NotifyOnEqualSet, true, true) { } public PropertyChangedBuilder(bool notifyOnEqualSet, bool useReferenceEquals, bool skipSetterOnNoChange) { _notifyOnEqualSet = notifyOnEqualSet; _useReferenceEquals = useReferenceEquals; _skipSetterOnNoChange = skipSetterOnNoChange; } private readonly bool _notifyOnEqualSet; private readonly bool _useReferenceEquals; private readonly bool _skipSetterOnNoChange; public override bool IsApplied(BuildContext context, AbstractTypeBuilderList builders) { if (context == null) throw new ArgumentNullException("context"); return context.IsSetter && (context.IsBeforeStep || context.IsAfterStep); } protected override void BeforeBuildAbstractSetter() { if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) GenerateIsSameValueComparison(); } protected override void BeforeBuildVirtualSetter() { if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) GenerateIsSameValueComparison(); } protected override void AfterBuildAbstractSetter() { BuildSetter(); } protected override void AfterBuildVirtualSetter() { BuildSetter(); } public override bool IsCompatible(BuildContext context, IAbstractTypeBuilder typeBuilder) { if (typeBuilder is PropertyChangedBuilder) return false; return base.IsCompatible(context, typeBuilder); } public override int GetPriority(BuildContext context) { return TypeBuilderConsts.Priority.PropChange; } private LocalBuilder _isSameValueBuilder; private Label _afterNotificationLabel; private void GenerateIsSameValueComparison() { EmitHelper emit = Context.MethodBuilder.Emitter; if (_skipSetterOnNoChange) _afterNotificationLabel = emit.DefineLabel(); else _isSameValueBuilder = emit.DeclareLocal(typeof(bool)); MethodInfo op_InequalityMethod = Context.CurrentProperty.PropertyType.GetMethod("op_Inequality", new Type[] { Context.CurrentProperty.PropertyType, Context.CurrentProperty.PropertyType }); if (op_InequalityMethod == null) { if (Context.CurrentProperty.PropertyType.IsValueType) { if (TypeHelper.IsNullableType(Context.CurrentProperty.PropertyType)) { // Handled nullable types var currentValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); var newValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); var notEqualLabel = emit.DefineLabel(); var comparedLabel = emit.DefineLabel(); var hasValueGetMethod = Context.CurrentProperty.PropertyType.GetProperty("HasValue").GetGetMethod(); emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .stloc(currentValue) .ldarg_1 .stloc(newValue) .ldloca(currentValue) .call(Context.CurrentProperty.PropertyType, "GetValueOrDefault") .ldloca(newValue) .call(Context.CurrentProperty.PropertyType, "GetValueOrDefault"); var nullableUnderlyingType = TypeHelper.GetUnderlyingType(Context.CurrentProperty.PropertyType); op_InequalityMethod = nullableUnderlyingType.GetMethod("op_Inequality", new Type[] { nullableUnderlyingType, nullableUnderlyingType }); if (op_InequalityMethod != null) { emit .call(op_InequalityMethod) .brtrue_s(notEqualLabel); } else emit.bne_un_s(notEqualLabel); emit .ldloca(currentValue) .call(hasValueGetMethod) .ldloca(newValue) .call(hasValueGetMethod) .ceq .ldc_bool(true) .ceq .br(comparedLabel) .MarkLabel(notEqualLabel) .ldc_bool(false) .MarkLabel(comparedLabel) .end(); } else if (!Context.CurrentProperty.PropertyType.IsPrimitive) { // Handle structs without op_Inequality. var currentValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .stloc(currentValue) .ldloca(currentValue) .ldarg_1 .box(Context.CurrentProperty.PropertyType) .constrained(Context.CurrentProperty.PropertyType) .callvirt(typeof(object), "Equals", new [] {typeof(object)}) .end(); } else { // Primitive value type comparison emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .ldarg_1 .ceq .end(); } } else if (!_useReferenceEquals) { // Do not use ReferenceEquals comparison for objects emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .ldarg_1 .ceq .end(); } else { // ReferenceEquals comparison for objects emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .ldarg_1 .call(typeof(object), "ReferenceEquals", typeof(object), typeof(object)) .end(); } } else { // Items compared have op_Inequality operator (!=) emit .ldarg_0 .callvirt(Context.CurrentProperty.GetGetMethod(true)) .ldarg_1 .call(op_InequalityMethod) .ldc_i4_0 .ceq .end(); } if (_skipSetterOnNoChange) emit.brtrue(_afterNotificationLabel); else emit.stloc(_isSameValueBuilder); } private void BuildSetter() { InterfaceMapping im = Context.Type.GetInterfaceMap(typeof(IPropertyChanged)); MethodInfo mi = im.TargetMethods[0]; FieldBuilder ifb = GetPropertyInfoField(); EmitHelper emit = Context.MethodBuilder.Emitter; if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead && !_skipSetterOnNoChange) { _afterNotificationLabel = emit.DefineLabel(); emit .ldloc (_isSameValueBuilder) .brtrue(_afterNotificationLabel); } if (mi.IsPublic) { emit .ldarg_0 .ldsfld (ifb) .callvirt (mi) ; } else { emit .ldarg_0 .castclass (typeof(IPropertyChanged)) .ldsfld (ifb) .callvirt (im.InterfaceMethods[0]) ; } if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) emit.MarkLabel(_afterNotificationLabel); } } }