Mercurial > pub > bltoolkit
diff Source/TypeBuilder/Builders/PropertyChangedBuilder.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/TypeBuilder/Builders/PropertyChangedBuilder.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,250 @@ +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); + } + } +}