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);
+		}
+	}
+}