Mercurial > pub > bltoolkit
comparison Source/TypeBuilder/Builders/PropertyChangedBuilder.cs @ 0:f990fcb411a9
Копия текущей версии из github
| author | cin |
|---|---|
| date | Thu, 27 Mar 2014 21:46:09 +0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:f990fcb411a9 |
|---|---|
| 1 using System; | |
| 2 using System.Reflection; | |
| 3 using System.Reflection.Emit; | |
| 4 using BLToolkit.Reflection; | |
| 5 using BLToolkit.Reflection.Emit; | |
| 6 | |
| 7 namespace BLToolkit.TypeBuilder.Builders | |
| 8 { | |
| 9 public class PropertyChangedBuilder : AbstractTypeBuilderBase | |
| 10 { | |
| 11 public PropertyChangedBuilder() | |
| 12 : this(Common.Configuration.NotifyOnEqualSet, true, true) | |
| 13 { | |
| 14 } | |
| 15 | |
| 16 public PropertyChangedBuilder(bool notifyOnEqualSet, bool useReferenceEquals, bool skipSetterOnNoChange) | |
| 17 { | |
| 18 _notifyOnEqualSet = notifyOnEqualSet; | |
| 19 _useReferenceEquals = useReferenceEquals; | |
| 20 _skipSetterOnNoChange = skipSetterOnNoChange; | |
| 21 } | |
| 22 | |
| 23 private readonly bool _notifyOnEqualSet; | |
| 24 private readonly bool _useReferenceEquals; | |
| 25 private readonly bool _skipSetterOnNoChange; | |
| 26 | |
| 27 public override bool IsApplied(BuildContext context, AbstractTypeBuilderList builders) | |
| 28 { | |
| 29 if (context == null) throw new ArgumentNullException("context"); | |
| 30 | |
| 31 return context.IsSetter && (context.IsBeforeStep || context.IsAfterStep); | |
| 32 } | |
| 33 | |
| 34 protected override void BeforeBuildAbstractSetter() | |
| 35 { | |
| 36 if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) | |
| 37 GenerateIsSameValueComparison(); | |
| 38 } | |
| 39 | |
| 40 protected override void BeforeBuildVirtualSetter() | |
| 41 { | |
| 42 if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) | |
| 43 GenerateIsSameValueComparison(); | |
| 44 } | |
| 45 | |
| 46 protected override void AfterBuildAbstractSetter() | |
| 47 { | |
| 48 BuildSetter(); | |
| 49 } | |
| 50 | |
| 51 protected override void AfterBuildVirtualSetter() | |
| 52 { | |
| 53 BuildSetter(); | |
| 54 } | |
| 55 | |
| 56 public override bool IsCompatible(BuildContext context, IAbstractTypeBuilder typeBuilder) | |
| 57 { | |
| 58 if (typeBuilder is PropertyChangedBuilder) | |
| 59 return false; | |
| 60 | |
| 61 return base.IsCompatible(context, typeBuilder); | |
| 62 } | |
| 63 | |
| 64 public override int GetPriority(BuildContext context) | |
| 65 { | |
| 66 return TypeBuilderConsts.Priority.PropChange; | |
| 67 } | |
| 68 | |
| 69 private LocalBuilder _isSameValueBuilder; | |
| 70 private Label _afterNotificationLabel; | |
| 71 | |
| 72 private void GenerateIsSameValueComparison() | |
| 73 { | |
| 74 EmitHelper emit = Context.MethodBuilder.Emitter; | |
| 75 | |
| 76 if (_skipSetterOnNoChange) | |
| 77 _afterNotificationLabel = emit.DefineLabel(); | |
| 78 else | |
| 79 _isSameValueBuilder = emit.DeclareLocal(typeof(bool)); | |
| 80 | |
| 81 MethodInfo op_InequalityMethod = | |
| 82 Context.CurrentProperty.PropertyType.GetMethod("op_Inequality", | |
| 83 new Type[] | |
| 84 { | |
| 85 Context.CurrentProperty.PropertyType, | |
| 86 Context.CurrentProperty.PropertyType | |
| 87 }); | |
| 88 | |
| 89 if (op_InequalityMethod == null) | |
| 90 { | |
| 91 if (Context.CurrentProperty.PropertyType.IsValueType) | |
| 92 { | |
| 93 if (TypeHelper.IsNullableType(Context.CurrentProperty.PropertyType)) | |
| 94 { | |
| 95 // Handled nullable types | |
| 96 | |
| 97 var currentValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); | |
| 98 var newValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); | |
| 99 var notEqualLabel = emit.DefineLabel(); | |
| 100 var comparedLabel = emit.DefineLabel(); | |
| 101 var hasValueGetMethod = Context.CurrentProperty.PropertyType.GetProperty("HasValue").GetGetMethod(); | |
| 102 | |
| 103 emit | |
| 104 .ldarg_0 | |
| 105 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 106 .stloc(currentValue) | |
| 107 .ldarg_1 | |
| 108 .stloc(newValue) | |
| 109 .ldloca(currentValue) | |
| 110 .call(Context.CurrentProperty.PropertyType, "GetValueOrDefault") | |
| 111 .ldloca(newValue) | |
| 112 .call(Context.CurrentProperty.PropertyType, "GetValueOrDefault"); | |
| 113 | |
| 114 var nullableUnderlyingType = TypeHelper.GetUnderlyingType(Context.CurrentProperty.PropertyType); | |
| 115 | |
| 116 op_InequalityMethod = nullableUnderlyingType.GetMethod("op_Inequality", | |
| 117 new Type[] | |
| 118 { | |
| 119 nullableUnderlyingType, | |
| 120 nullableUnderlyingType | |
| 121 }); | |
| 122 | |
| 123 if (op_InequalityMethod != null) | |
| 124 { | |
| 125 emit | |
| 126 .call(op_InequalityMethod) | |
| 127 .brtrue_s(notEqualLabel); | |
| 128 } | |
| 129 else | |
| 130 emit.bne_un_s(notEqualLabel); | |
| 131 | |
| 132 emit | |
| 133 .ldloca(currentValue) | |
| 134 .call(hasValueGetMethod) | |
| 135 .ldloca(newValue) | |
| 136 .call(hasValueGetMethod) | |
| 137 .ceq | |
| 138 .ldc_bool(true) | |
| 139 .ceq | |
| 140 .br(comparedLabel) | |
| 141 .MarkLabel(notEqualLabel) | |
| 142 .ldc_bool(false) | |
| 143 .MarkLabel(comparedLabel) | |
| 144 .end(); | |
| 145 } | |
| 146 else if (!Context.CurrentProperty.PropertyType.IsPrimitive) | |
| 147 { | |
| 148 // Handle structs without op_Inequality. | |
| 149 var currentValue = emit.DeclareLocal(Context.CurrentProperty.PropertyType); | |
| 150 | |
| 151 emit | |
| 152 .ldarg_0 | |
| 153 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 154 .stloc(currentValue) | |
| 155 .ldloca(currentValue) | |
| 156 .ldarg_1 | |
| 157 .box(Context.CurrentProperty.PropertyType) | |
| 158 .constrained(Context.CurrentProperty.PropertyType) | |
| 159 .callvirt(typeof(object), "Equals", new [] {typeof(object)}) | |
| 160 .end(); | |
| 161 } | |
| 162 else | |
| 163 { | |
| 164 // Primitive value type comparison | |
| 165 emit | |
| 166 .ldarg_0 | |
| 167 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 168 .ldarg_1 | |
| 169 .ceq | |
| 170 .end(); | |
| 171 } | |
| 172 } | |
| 173 else if (!_useReferenceEquals) | |
| 174 { | |
| 175 // Do not use ReferenceEquals comparison for objects | |
| 176 emit | |
| 177 .ldarg_0 | |
| 178 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 179 .ldarg_1 | |
| 180 .ceq | |
| 181 .end(); | |
| 182 } | |
| 183 else | |
| 184 { | |
| 185 // ReferenceEquals comparison for objects | |
| 186 emit | |
| 187 .ldarg_0 | |
| 188 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 189 .ldarg_1 | |
| 190 .call(typeof(object), "ReferenceEquals", typeof(object), typeof(object)) | |
| 191 .end(); | |
| 192 } | |
| 193 } | |
| 194 else | |
| 195 { | |
| 196 // Items compared have op_Inequality operator (!=) | |
| 197 emit | |
| 198 .ldarg_0 | |
| 199 .callvirt(Context.CurrentProperty.GetGetMethod(true)) | |
| 200 .ldarg_1 | |
| 201 .call(op_InequalityMethod) | |
| 202 .ldc_i4_0 | |
| 203 .ceq | |
| 204 .end(); | |
| 205 } | |
| 206 | |
| 207 if (_skipSetterOnNoChange) | |
| 208 emit.brtrue(_afterNotificationLabel); | |
| 209 else | |
| 210 emit.stloc(_isSameValueBuilder); | |
| 211 } | |
| 212 | |
| 213 private void BuildSetter() | |
| 214 { | |
| 215 InterfaceMapping im = Context.Type.GetInterfaceMap(typeof(IPropertyChanged)); | |
| 216 MethodInfo mi = im.TargetMethods[0]; | |
| 217 FieldBuilder ifb = GetPropertyInfoField(); | |
| 218 EmitHelper emit = Context.MethodBuilder.Emitter; | |
| 219 | |
| 220 if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead && !_skipSetterOnNoChange) | |
| 221 { | |
| 222 _afterNotificationLabel = emit.DefineLabel(); | |
| 223 emit | |
| 224 .ldloc (_isSameValueBuilder) | |
| 225 .brtrue(_afterNotificationLabel); | |
| 226 } | |
| 227 | |
| 228 if (mi.IsPublic) | |
| 229 { | |
| 230 emit | |
| 231 .ldarg_0 | |
| 232 .ldsfld (ifb) | |
| 233 .callvirt (mi) | |
| 234 ; | |
| 235 } | |
| 236 else | |
| 237 { | |
| 238 emit | |
| 239 .ldarg_0 | |
| 240 .castclass (typeof(IPropertyChanged)) | |
| 241 .ldsfld (ifb) | |
| 242 .callvirt (im.InterfaceMethods[0]) | |
| 243 ; | |
| 244 } | |
| 245 | |
| 246 if (!_notifyOnEqualSet && Context.CurrentProperty.CanRead) | |
| 247 emit.MarkLabel(_afterNotificationLabel); | |
| 248 } | |
| 249 } | |
| 250 } |
