PropertyDecorator.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. #if !NO_RUNTIME
  2. using System;
  3. using ProtoBuf.Meta;
  4. #if FEAT_IKVM
  5. using Type = IKVM.Reflection.Type;
  6. using IKVM.Reflection;
  7. #else
  8. using System.Reflection;
  9. #endif
  10. namespace ProtoBuf.Serializers
  11. {
  12. sealed class PropertyDecorator : ProtoDecoratorBase
  13. {
  14. public override Type ExpectedType { get { return forType; } }
  15. private readonly PropertyInfo property;
  16. private readonly Type forType;
  17. public override bool RequiresOldValue { get { return true; } }
  18. public override bool ReturnsValue { get { return false; } }
  19. private readonly bool readOptionsWriteValue;
  20. private readonly MethodInfo shadowSetter;
  21. public PropertyDecorator(TypeModel model, Type forType, PropertyInfo property, IProtoSerializer tail) : base(tail)
  22. {
  23. Helpers.DebugAssert(forType != null);
  24. Helpers.DebugAssert(property != null);
  25. this.forType = forType;
  26. this.property = property;
  27. SanityCheck(model, property, tail, out readOptionsWriteValue, true, true);
  28. shadowSetter = GetShadowSetter(model, property);
  29. }
  30. private static void SanityCheck(TypeModel model, PropertyInfo property, IProtoSerializer tail, out bool writeValue, bool nonPublic, bool allowInternal) {
  31. if(property == null) throw new ArgumentNullException("property");
  32. writeValue = tail.ReturnsValue && (GetShadowSetter(model, property) != null || (property.CanWrite && Helpers.GetSetMethod(property, nonPublic, allowInternal) != null));
  33. if (!property.CanRead || Helpers.GetGetMethod(property, nonPublic, allowInternal) == null)
  34. {
  35. throw new InvalidOperationException("Cannot serialize property without a get accessor");
  36. }
  37. if (!writeValue && (!tail.RequiresOldValue || Helpers.IsValueType(tail.ExpectedType)))
  38. { // so we can't save the value, and the tail doesn't use it either... not helpful
  39. // or: can't write the value, so the struct value will be lost
  40. throw new InvalidOperationException("Cannot apply changes to property " + property.DeclaringType.FullName + "." + property.Name);
  41. }
  42. }
  43. static MethodInfo GetShadowSetter(TypeModel model, PropertyInfo property)
  44. {
  45. #if WINRT || COREFX
  46. MethodInfo method = Helpers.GetInstanceMethod(property.DeclaringType.GetTypeInfo(), "Set" + property.Name, new Type[] { property.PropertyType });
  47. #else
  48. #if FEAT_IKVM
  49. Type reflectedType = property.DeclaringType;
  50. #else
  51. Type reflectedType = property.ReflectedType;
  52. #endif
  53. MethodInfo method = Helpers.GetInstanceMethod(reflectedType, "Set" + property.Name, new Type[] { property.PropertyType });
  54. #endif
  55. if (method == null || !method.IsPublic || method.ReturnType != model.MapType(typeof(void))) return null;
  56. return method;
  57. }
  58. #if !FEAT_IKVM
  59. public override void Write(object value, ProtoWriter dest)
  60. {
  61. Helpers.DebugAssert(value != null);
  62. value = property.GetValue(value, null);
  63. if(value != null) Tail.Write(value, dest);
  64. }
  65. public override object Read(object value, ProtoReader source)
  66. {
  67. Helpers.DebugAssert(value != null);
  68. object oldVal = Tail.RequiresOldValue ? property.GetValue(value, null) : null;
  69. object newVal = Tail.Read(oldVal, source);
  70. if (readOptionsWriteValue && newVal != null) // if the tail returns a null, intepret that as *no assign*
  71. {
  72. if (shadowSetter == null)
  73. {
  74. property.SetValue(value, newVal, null);
  75. }
  76. else
  77. {
  78. shadowSetter.Invoke(value, new object[] { newVal });
  79. }
  80. }
  81. return null;
  82. }
  83. #endif
  84. #if FEAT_COMPILER
  85. protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
  86. {
  87. ctx.LoadAddress(valueFrom, ExpectedType);
  88. ctx.LoadValue(property);
  89. ctx.WriteNullCheckedTail(property.PropertyType, Tail, null);
  90. }
  91. protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
  92. {
  93. bool writeValue;
  94. SanityCheck(ctx.Model, property, Tail, out writeValue, ctx.NonPublic, ctx.AllowInternal(property));
  95. if (Helpers.IsValueType(ExpectedType) && valueFrom == null)
  96. {
  97. throw new InvalidOperationException("Attempt to mutate struct on the head of the stack; changes would be lost");
  98. }
  99. using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom))
  100. {
  101. if (Tail.RequiresOldValue)
  102. {
  103. ctx.LoadAddress(loc, ExpectedType); // stack is: old-addr
  104. ctx.LoadValue(property); // stack is: old-value
  105. }
  106. Type propertyType = property.PropertyType;
  107. ctx.ReadNullCheckedTail(propertyType, Tail, null); // stack is [new-value]
  108. if (writeValue)
  109. {
  110. using (Compiler.Local newVal = new Compiler.Local(ctx, property.PropertyType))
  111. {
  112. ctx.StoreValue(newVal); // stack is empty
  113. Compiler.CodeLabel allDone = new Compiler.CodeLabel(); // <=== default structs
  114. if (!Helpers.IsValueType(propertyType))
  115. { // if the tail returns a null, intepret that as *no assign*
  116. allDone = ctx.DefineLabel();
  117. ctx.LoadValue(newVal); // stack is: new-value
  118. ctx.BranchIfFalse(@allDone, true); // stack is empty
  119. }
  120. // assign the value
  121. ctx.LoadAddress(loc, ExpectedType); // parent-addr
  122. ctx.LoadValue(newVal); // parent-obj|new-value
  123. if (shadowSetter == null)
  124. {
  125. ctx.StoreValue(property); // empty
  126. }
  127. else
  128. {
  129. ctx.EmitCall(shadowSetter); // empty
  130. }
  131. if (!Helpers.IsValueType(propertyType))
  132. {
  133. ctx.MarkLabel(allDone);
  134. }
  135. }
  136. }
  137. else
  138. { // don't want return value; drop it if anything there
  139. // stack is [new-value]
  140. if (Tail.ReturnsValue) { ctx.DiscardValue(); }
  141. }
  142. }
  143. }
  144. #endif
  145. internal static bool CanWrite(TypeModel model, MemberInfo member)
  146. {
  147. if (member == null) throw new ArgumentNullException("member");
  148. PropertyInfo prop = member as PropertyInfo;
  149. if (prop != null) return prop.CanWrite || GetShadowSetter(model, prop) != null;
  150. return member is FieldInfo; // fields are always writeable; anything else: JUST SAY NO!
  151. }
  152. }
  153. }
  154. #endif