ImmutableCollectionDecorator.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. #if !NO_RUNTIME
  2. using System;
  3. using System.Collections;
  4. using ProtoBuf.Meta;
  5. #if FEAT_IKVM
  6. using Type = IKVM.Reflection.Type;
  7. using IKVM.Reflection;
  8. #else
  9. using System.Reflection;
  10. #endif
  11. namespace ProtoBuf.Serializers
  12. {
  13. sealed class ImmutableCollectionDecorator : ListDecorator
  14. {
  15. protected override bool RequireAdd { get { return false; } }
  16. #if !NO_GENERICS
  17. static Type ResolveIReadOnlyCollection(Type declaredType, Type t)
  18. {
  19. #if WINRT || COREFX
  20. foreach (Type intImplBasic in declaredType.GetTypeInfo().ImplementedInterfaces)
  21. {
  22. TypeInfo intImpl = intImplBasic.GetTypeInfo();
  23. if (intImpl.IsGenericType && intImpl.Name.StartsWith("IReadOnlyCollection`"))
  24. {
  25. if(t != null)
  26. {
  27. Type[] typeArgs = intImpl.GenericTypeArguments;
  28. if (typeArgs.Length != 1 && typeArgs[0] != t) continue;
  29. }
  30. return intImplBasic;
  31. }
  32. }
  33. #else
  34. foreach (Type intImpl in declaredType.GetInterfaces())
  35. {
  36. if (intImpl.IsGenericType && intImpl.Name.StartsWith("IReadOnlyCollection`"))
  37. {
  38. if(t != null)
  39. {
  40. Type[] typeArgs = intImpl.GetGenericArguments();
  41. if (typeArgs.Length != 1 && typeArgs[0] != t) continue;
  42. }
  43. return intImpl;
  44. }
  45. }
  46. #endif
  47. return null;
  48. }
  49. internal static bool IdentifyImmutable(TypeModel model, Type declaredType, out MethodInfo builderFactory, out MethodInfo add, out MethodInfo addRange, out MethodInfo finish)
  50. {
  51. builderFactory = add = addRange = finish = null;
  52. if (model == null || declaredType == null) return false;
  53. #if WINRT || COREFX
  54. TypeInfo declaredTypeInfo = declaredType.GetTypeInfo();
  55. #else
  56. Type declaredTypeInfo = declaredType;
  57. #endif
  58. // try to detect immutable collections; firstly, they are all generic, and all implement IReadOnlyCollection<T> for some T
  59. if(!declaredTypeInfo.IsGenericType) return false;
  60. #if WINRT || COREFX
  61. Type[] typeArgs = declaredTypeInfo.GenericTypeArguments, effectiveType;
  62. #else
  63. Type[] typeArgs = declaredTypeInfo.GetGenericArguments(), effectiveType;
  64. #endif
  65. switch (typeArgs.Length)
  66. {
  67. case 1:
  68. effectiveType = typeArgs;
  69. break; // fine
  70. case 2:
  71. Type kvp = model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>));
  72. if (kvp == null) return false;
  73. kvp = kvp.MakeGenericType(typeArgs);
  74. effectiveType = new Type[] { kvp };
  75. break;
  76. default:
  77. return false; // no clue!
  78. }
  79. if (ResolveIReadOnlyCollection(declaredType, null) == null) return false; // no IReadOnlyCollection<T> found
  80. // and we want to use the builder API, so for generic Foo<T> or IFoo<T> we want to use Foo.CreateBuilder<T>
  81. string name = declaredType.Name;
  82. int i = name.IndexOf('`');
  83. if (i <= 0) return false;
  84. name = declaredTypeInfo.IsInterface ? name.Substring(1, i - 1) : name.Substring(0, i);
  85. Type outerType = model.GetType(declaredType.Namespace + "." + name, declaredTypeInfo.Assembly);
  86. // I hate special-cases...
  87. if (outerType == null && name == "ImmutableSet")
  88. {
  89. outerType = model.GetType(declaredType.Namespace + ".ImmutableHashSet", declaredTypeInfo.Assembly);
  90. }
  91. if (outerType == null) return false;
  92. #if WINRT
  93. foreach (MethodInfo method in outerType.GetTypeInfo().DeclaredMethods)
  94. #else
  95. foreach (MethodInfo method in outerType.GetMethods())
  96. #endif
  97. {
  98. if (!method.IsStatic || method.Name != "CreateBuilder" || !method.IsGenericMethodDefinition || method.GetParameters().Length != 0
  99. || method.GetGenericArguments().Length != typeArgs.Length) continue;
  100. builderFactory = method.MakeGenericMethod(typeArgs);
  101. break;
  102. }
  103. Type voidType = model.MapType(typeof(void));
  104. if (builderFactory == null || builderFactory.ReturnType == null || builderFactory.ReturnType == voidType) return false;
  105. add = Helpers.GetInstanceMethod(builderFactory.ReturnType, "Add", effectiveType);
  106. if (add == null) return false;
  107. finish = Helpers.GetInstanceMethod(builderFactory.ReturnType, "ToImmutable", Helpers.EmptyTypes);
  108. if (finish == null || finish.ReturnType == null || finish.ReturnType == voidType) return false;
  109. if (!(finish.ReturnType == declaredType || Helpers.IsAssignableFrom(declaredType, finish.ReturnType))) return false;
  110. addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[] { declaredType });
  111. if (addRange == null)
  112. {
  113. Type enumerable = model.MapType(typeof(System.Collections.Generic.IEnumerable<>), false);
  114. if (enumerable != null)
  115. {
  116. addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[] { enumerable.MakeGenericType(effectiveType) });
  117. }
  118. }
  119. return true;
  120. }
  121. #endif
  122. private readonly MethodInfo builderFactory, add, addRange, finish;
  123. internal ImmutableCollectionDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull,
  124. MethodInfo builderFactory, MethodInfo add, MethodInfo addRange, MethodInfo finish)
  125. : base(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull)
  126. {
  127. this.builderFactory = builderFactory;
  128. this.add = add;
  129. this.addRange = addRange;
  130. this.finish = finish;
  131. }
  132. #if !FEAT_IKVM
  133. public override object Read(object value, ProtoReader source)
  134. {
  135. object builderInstance = builderFactory.Invoke(null, null);
  136. int field = source.FieldNumber;
  137. object[] args = new object[1];
  138. if (AppendToCollection && value != null && ((IList)value).Count != 0)
  139. {
  140. if(addRange !=null)
  141. {
  142. args[0] = value;
  143. addRange.Invoke(builderInstance, args);
  144. }
  145. else
  146. {
  147. foreach(object item in (IList)value)
  148. {
  149. args[0] = item;
  150. add.Invoke(builderInstance, args);
  151. }
  152. }
  153. }
  154. if (packedWireType != WireType.None && source.WireType == WireType.String)
  155. {
  156. SubItemToken token = ProtoReader.StartSubItem(source);
  157. while (ProtoReader.HasSubValue(packedWireType, source))
  158. {
  159. args[0] = Tail.Read(null, source);
  160. add.Invoke(builderInstance, args);
  161. }
  162. ProtoReader.EndSubItem(token, source);
  163. }
  164. else
  165. {
  166. do
  167. {
  168. args[0] = Tail.Read(null, source);
  169. add.Invoke(builderInstance, args);
  170. } while (source.TryReadFieldHeader(field));
  171. }
  172. return finish.Invoke(builderInstance, null);
  173. }
  174. #endif
  175. #if FEAT_COMPILER
  176. protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
  177. {
  178. using (Compiler.Local oldList = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : null)
  179. using(Compiler.Local builder = new Compiler.Local(ctx, builderFactory.ReturnType))
  180. {
  181. ctx.EmitCall(builderFactory);
  182. ctx.StoreValue(builder);
  183. if(AppendToCollection)
  184. {
  185. Compiler.CodeLabel done = ctx.DefineLabel();
  186. if(!Helpers.IsValueType(ExpectedType))
  187. {
  188. ctx.LoadValue(oldList);
  189. ctx.BranchIfFalse(done, false); // old value null; nothing to add
  190. }
  191. #if COREFX
  192. TypeInfo typeInfo = ExpectedType.GetTypeInfo();
  193. #else
  194. Type typeInfo = ExpectedType;
  195. #endif
  196. PropertyInfo prop = Helpers.GetProperty(typeInfo, "Length", false);
  197. if(prop == null) prop = Helpers.GetProperty(typeInfo, "Count", false);
  198. #if !NO_GENERICS
  199. if (prop == null) prop = Helpers.GetProperty(ResolveIReadOnlyCollection(ExpectedType, Tail.ExpectedType), "Count", false);
  200. #endif
  201. ctx.LoadAddress(oldList, oldList.Type);
  202. ctx.EmitCall(Helpers.GetGetMethod(prop, false, false));
  203. ctx.BranchIfFalse(done, false); // old list is empty; nothing to add
  204. Type voidType = ctx.MapType(typeof(void));
  205. if(addRange != null)
  206. {
  207. ctx.LoadValue(builder);
  208. ctx.LoadValue(oldList);
  209. ctx.EmitCall(addRange);
  210. if (addRange.ReturnType != null && add.ReturnType != voidType) ctx.DiscardValue();
  211. }
  212. else
  213. {
  214. // loop and call Add repeatedly
  215. MethodInfo moveNext, current, getEnumerator = GetEnumeratorInfo(ctx.Model, out moveNext, out current);
  216. Helpers.DebugAssert(moveNext != null);
  217. Helpers.DebugAssert(current != null);
  218. Helpers.DebugAssert(getEnumerator != null);
  219. Type enumeratorType = getEnumerator.ReturnType;
  220. using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType))
  221. {
  222. ctx.LoadAddress(oldList, ExpectedType);
  223. ctx.EmitCall(getEnumerator);
  224. ctx.StoreValue(iter);
  225. using (ctx.Using(iter))
  226. {
  227. Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel();
  228. ctx.Branch(next, false);
  229. ctx.MarkLabel(body);
  230. ctx.LoadAddress(builder, builder.Type);
  231. ctx.LoadAddress(iter, enumeratorType);
  232. ctx.EmitCall(current);
  233. ctx.EmitCall(add);
  234. if (add.ReturnType != null && add.ReturnType != voidType) ctx.DiscardValue();
  235. ctx.MarkLabel(@next);
  236. ctx.LoadAddress(iter, enumeratorType);
  237. ctx.EmitCall(moveNext);
  238. ctx.BranchIfTrue(body, false);
  239. }
  240. }
  241. }
  242. ctx.MarkLabel(done);
  243. }
  244. EmitReadList(ctx, builder, Tail, add, packedWireType, false);
  245. ctx.LoadAddress(builder, builder.Type);
  246. ctx.EmitCall(finish);
  247. if(ExpectedType != finish.ReturnType)
  248. {
  249. ctx.Cast(ExpectedType);
  250. }
  251. }
  252. }
  253. #endif
  254. }
  255. }
  256. #endif