BclHelpers.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. using System;
  2. using System.Reflection;
  3. namespace ProtoBuf
  4. {
  5. internal enum TimeSpanScale
  6. {
  7. Days = 0,
  8. Hours = 1,
  9. Minutes = 2,
  10. Seconds = 3,
  11. Milliseconds = 4,
  12. Ticks = 5,
  13. MinMax = 15
  14. }
  15. /// <summary>
  16. /// Provides support for common .NET types that do not have a direct representation
  17. /// in protobuf, using the definitions from bcl.proto
  18. /// </summary>
  19. public
  20. #if FX11
  21. sealed
  22. #else
  23. static
  24. #endif
  25. class BclHelpers
  26. {
  27. /// <summary>
  28. /// Creates a new instance of the specified type, bypassing the constructor.
  29. /// </summary>
  30. /// <param name="type">The type to create</param>
  31. /// <returns>The new instance</returns>
  32. /// <exception cref="NotSupportedException">If the platform does not support constructor-skipping</exception>
  33. public static object GetUninitializedObject(Type type)
  34. {
  35. #if COREFX
  36. object obj = TryGetUninitializedObjectWithFormatterServices(type);
  37. if (obj != null) return obj;
  38. #endif
  39. #if PLAT_BINARYFORMATTER && !(WINRT || PHONE8 || COREFX)
  40. return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
  41. #else
  42. throw new NotSupportedException("Constructor-skipping is not supported on this platform");
  43. #endif
  44. }
  45. #if COREFX // this is inspired by DCS: https://github.com/dotnet/corefx/blob/c02d33b18398199f6acc17d375dab154e9a1df66/src/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlFormatReaderGenerator.cs#L854-L894
  46. static Func<Type, object> getUninitializedObject;
  47. static internal object TryGetUninitializedObjectWithFormatterServices(Type type)
  48. {
  49. if (getUninitializedObject == null)
  50. {
  51. try {
  52. var formatterServiceType = typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices");
  53. MethodInfo method = formatterServiceType?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
  54. if (method != null)
  55. {
  56. getUninitializedObject = (Func<Type, object>)method.CreateDelegate(typeof(Func<Type, object>));
  57. }
  58. }
  59. catch { /* best efforts only */ }
  60. if(getUninitializedObject == null) getUninitializedObject = x => null;
  61. }
  62. return getUninitializedObject(type);
  63. }
  64. #endif
  65. #if FX11
  66. private BclHelpers() { } // not a static class for C# 1.2 reasons
  67. #endif
  68. const int FieldTimeSpanValue = 0x01, FieldTimeSpanScale = 0x02, FieldTimeSpanKind = 0x03;
  69. internal static readonly DateTime[] EpochOrigin = {
  70. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
  71. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
  72. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local)
  73. };
  74. /// <summary>
  75. /// The default value for dates that are following google.protobuf.Timestamp semantics
  76. /// </summary>
  77. private static readonly DateTime TimestampEpoch = EpochOrigin[(int)DateTimeKind.Utc];
  78. /// <summary>
  79. /// Writes a TimeSpan to a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
  80. /// </summary>
  81. public static void WriteTimeSpan(TimeSpan timeSpan, ProtoWriter dest)
  82. {
  83. WriteTimeSpanImpl(timeSpan, dest, DateTimeKind.Unspecified);
  84. }
  85. private static void WriteTimeSpanImpl(TimeSpan timeSpan, ProtoWriter dest, DateTimeKind kind)
  86. {
  87. if (dest == null) throw new ArgumentNullException("dest");
  88. long value;
  89. switch(dest.WireType)
  90. {
  91. case WireType.String:
  92. case WireType.StartGroup:
  93. TimeSpanScale scale;
  94. value = timeSpan.Ticks;
  95. if (timeSpan == TimeSpan.MaxValue)
  96. {
  97. value = 1;
  98. scale = TimeSpanScale.MinMax;
  99. }
  100. else if (timeSpan == TimeSpan.MinValue)
  101. {
  102. value = -1;
  103. scale = TimeSpanScale.MinMax;
  104. }
  105. else if (value % TimeSpan.TicksPerDay == 0)
  106. {
  107. scale = TimeSpanScale.Days;
  108. value /= TimeSpan.TicksPerDay;
  109. }
  110. else if (value % TimeSpan.TicksPerHour == 0)
  111. {
  112. scale = TimeSpanScale.Hours;
  113. value /= TimeSpan.TicksPerHour;
  114. }
  115. else if (value % TimeSpan.TicksPerMinute == 0)
  116. {
  117. scale = TimeSpanScale.Minutes;
  118. value /= TimeSpan.TicksPerMinute;
  119. }
  120. else if (value % TimeSpan.TicksPerSecond == 0)
  121. {
  122. scale = TimeSpanScale.Seconds;
  123. value /= TimeSpan.TicksPerSecond;
  124. }
  125. else if (value % TimeSpan.TicksPerMillisecond == 0)
  126. {
  127. scale = TimeSpanScale.Milliseconds;
  128. value /= TimeSpan.TicksPerMillisecond;
  129. }
  130. else
  131. {
  132. scale = TimeSpanScale.Ticks;
  133. }
  134. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  135. if(value != 0) {
  136. ProtoWriter.WriteFieldHeader(FieldTimeSpanValue, WireType.SignedVariant, dest);
  137. ProtoWriter.WriteInt64(value, dest);
  138. }
  139. if(scale != TimeSpanScale.Days) {
  140. ProtoWriter.WriteFieldHeader(FieldTimeSpanScale, WireType.Variant, dest);
  141. ProtoWriter.WriteInt32((int)scale, dest);
  142. }
  143. if(kind != DateTimeKind.Unspecified)
  144. {
  145. ProtoWriter.WriteFieldHeader(FieldTimeSpanKind, WireType.Variant, dest);
  146. ProtoWriter.WriteInt32((int)kind, dest);
  147. }
  148. ProtoWriter.EndSubItem(token, dest);
  149. break;
  150. case WireType.Fixed64:
  151. ProtoWriter.WriteInt64(timeSpan.Ticks, dest);
  152. break;
  153. default:
  154. throw new ProtoException("Unexpected wire-type: " + dest.WireType.ToString());
  155. }
  156. }
  157. /// <summary>
  158. /// Parses a TimeSpan from a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
  159. /// </summary>
  160. public static TimeSpan ReadTimeSpan(ProtoReader source)
  161. {
  162. DateTimeKind kind;
  163. long ticks = ReadTimeSpanTicks(source, out kind);
  164. if (ticks == long.MinValue) return TimeSpan.MinValue;
  165. if (ticks == long.MaxValue) return TimeSpan.MaxValue;
  166. return TimeSpan.FromTicks(ticks);
  167. }
  168. /// <summary>
  169. /// Parses a TimeSpan from a protobuf stream using the standardized format, google.protobuf.Duration
  170. /// </summary>
  171. public static TimeSpan ReadDuration(ProtoReader source)
  172. {
  173. long seconds = 0;
  174. int nanos = 0;
  175. SubItemToken token = ProtoReader.StartSubItem(source);
  176. int fieldNumber;
  177. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  178. {
  179. switch (fieldNumber)
  180. {
  181. case 1:
  182. seconds = source.ReadInt64();
  183. break;
  184. case 2:
  185. nanos = source.ReadInt32();
  186. break;
  187. default:
  188. source.SkipField();
  189. break;
  190. }
  191. }
  192. ProtoReader.EndSubItem(token, source);
  193. return FromDurationSeconds(seconds, nanos);
  194. }
  195. /// <summary>
  196. /// Writes a TimeSpan to a protobuf stream using the standardized format, google.protobuf.Duration
  197. /// </summary>
  198. public static void WriteDuration(TimeSpan value, ProtoWriter dest)
  199. {
  200. int nanos;
  201. var seconds = ToDurationSeconds(value, out nanos);
  202. WriteSecondsNanos(seconds, nanos, dest);
  203. }
  204. private static void WriteSecondsNanos(long seconds, int nanos, ProtoWriter dest)
  205. {
  206. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  207. if (seconds != 0)
  208. {
  209. ProtoWriter.WriteFieldHeader(1, WireType.Variant, dest);
  210. ProtoWriter.WriteInt64(seconds, dest);
  211. }
  212. if (nanos != 0)
  213. {
  214. ProtoWriter.WriteFieldHeader(2, WireType.Variant, dest);
  215. ProtoWriter.WriteInt32(nanos, dest);
  216. }
  217. ProtoWriter.EndSubItem(token, dest);
  218. }
  219. /// <summary>
  220. /// Parses a DateTime from a protobuf stream using the standardized format, google.protobuf.Timestamp
  221. /// </summary>
  222. public static DateTime ReadTimestamp(ProtoReader source)
  223. {
  224. // note: DateTime is only defined for just over 0000 to just below 10000;
  225. // TimeSpan has a range of +/- 10,675,199 days === 29k years;
  226. // so we can just use epoch time delta
  227. return TimestampEpoch + ReadDuration(source);
  228. }
  229. /// <summary>
  230. /// Writes a DateTime to a protobuf stream using the standardized format, google.protobuf.Timestamp
  231. /// </summary>
  232. public static void WriteTimestamp(DateTime value, ProtoWriter dest)
  233. {
  234. int nanos;
  235. var seconds = ToDurationSeconds(value - TimestampEpoch, out nanos);
  236. if (nanos < 0)
  237. { // from Timestamp.proto:
  238. // "Negative second values with fractions must still have
  239. // non -negative nanos values that count forward in time."
  240. seconds--;
  241. nanos += 1000000000;
  242. }
  243. WriteSecondsNanos(seconds, nanos, dest);
  244. }
  245. static TimeSpan FromDurationSeconds(long seconds, int nanos)
  246. {
  247. long ticks = checked((seconds * TimeSpan.TicksPerSecond)
  248. + (nanos * TimeSpan.TicksPerMillisecond) / 1000000);
  249. return TimeSpan.FromTicks(ticks);
  250. }
  251. static long ToDurationSeconds(TimeSpan value, out int nanos)
  252. {
  253. nanos = (int)(((value.Ticks % TimeSpan.TicksPerSecond) * 1000000)
  254. / TimeSpan.TicksPerMillisecond);
  255. return value.Ticks / TimeSpan.TicksPerSecond;
  256. }
  257. /// <summary>
  258. /// Parses a DateTime from a protobuf stream
  259. /// </summary>
  260. public static DateTime ReadDateTime(ProtoReader source)
  261. {
  262. DateTimeKind kind;
  263. long ticks = ReadTimeSpanTicks(source, out kind);
  264. if (ticks == long.MinValue) return DateTime.MinValue;
  265. if (ticks == long.MaxValue) return DateTime.MaxValue;
  266. return EpochOrigin[(int)kind].AddTicks(ticks);
  267. }
  268. /// <summary>
  269. /// Writes a DateTime to a protobuf stream, excluding the <c>Kind</c>
  270. /// </summary>
  271. public static void WriteDateTime(DateTime value, ProtoWriter dest)
  272. {
  273. WriteDateTimeImpl(value, dest, false);
  274. }
  275. /// <summary>
  276. /// Writes a DateTime to a protobuf stream, including the <c>Kind</c>
  277. /// </summary>
  278. public static void WriteDateTimeWithKind(DateTime value, ProtoWriter dest)
  279. {
  280. WriteDateTimeImpl(value, dest, true);
  281. }
  282. private static void WriteDateTimeImpl(DateTime value, ProtoWriter dest, bool includeKind)
  283. {
  284. if (dest == null) throw new ArgumentNullException("dest");
  285. TimeSpan delta;
  286. switch (dest.WireType)
  287. {
  288. case WireType.StartGroup:
  289. case WireType.String:
  290. if (value == DateTime.MaxValue)
  291. {
  292. delta = TimeSpan.MaxValue;
  293. includeKind = false;
  294. }
  295. else if (value == DateTime.MinValue)
  296. {
  297. delta = TimeSpan.MinValue;
  298. includeKind = false;
  299. }
  300. else
  301. {
  302. delta = value - EpochOrigin[0];
  303. }
  304. break;
  305. default:
  306. delta = value - EpochOrigin[0];
  307. break;
  308. }
  309. WriteTimeSpanImpl(delta, dest, includeKind ? value.Kind : DateTimeKind.Unspecified);
  310. }
  311. private static long ReadTimeSpanTicks(ProtoReader source, out DateTimeKind kind) {
  312. kind = DateTimeKind.Unspecified;
  313. switch (source.WireType)
  314. {
  315. case WireType.String:
  316. case WireType.StartGroup:
  317. SubItemToken token = ProtoReader.StartSubItem(source);
  318. int fieldNumber;
  319. TimeSpanScale scale = TimeSpanScale.Days;
  320. long value = 0;
  321. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  322. {
  323. switch (fieldNumber)
  324. {
  325. case FieldTimeSpanScale:
  326. scale = (TimeSpanScale)source.ReadInt32();
  327. break;
  328. case FieldTimeSpanValue:
  329. source.Assert(WireType.SignedVariant);
  330. value = source.ReadInt64();
  331. break;
  332. case FieldTimeSpanKind:
  333. kind = (DateTimeKind)source.ReadInt32();
  334. switch(kind)
  335. {
  336. case DateTimeKind.Unspecified:
  337. case DateTimeKind.Utc:
  338. case DateTimeKind.Local:
  339. break; // fine
  340. default:
  341. throw new ProtoException("Invalid date/time kind: " + kind.ToString());
  342. }
  343. break;
  344. default:
  345. source.SkipField();
  346. break;
  347. }
  348. }
  349. ProtoReader.EndSubItem(token, source);
  350. switch (scale)
  351. {
  352. case TimeSpanScale.Days:
  353. return value * TimeSpan.TicksPerDay;
  354. case TimeSpanScale.Hours:
  355. return value * TimeSpan.TicksPerHour;
  356. case TimeSpanScale.Minutes:
  357. return value * TimeSpan.TicksPerMinute;
  358. case TimeSpanScale.Seconds:
  359. return value * TimeSpan.TicksPerSecond;
  360. case TimeSpanScale.Milliseconds:
  361. return value * TimeSpan.TicksPerMillisecond;
  362. case TimeSpanScale.Ticks:
  363. return value;
  364. case TimeSpanScale.MinMax:
  365. switch (value)
  366. {
  367. case 1: return long.MaxValue;
  368. case -1: return long.MinValue;
  369. default: throw new ProtoException("Unknown min/max value: " + value.ToString());
  370. }
  371. default:
  372. throw new ProtoException("Unknown timescale: " + scale.ToString());
  373. }
  374. case WireType.Fixed64:
  375. return source.ReadInt64();
  376. default:
  377. throw new ProtoException("Unexpected wire-type: " + source.WireType.ToString());
  378. }
  379. }
  380. const int FieldDecimalLow = 0x01, FieldDecimalHigh = 0x02, FieldDecimalSignScale = 0x03;
  381. /// <summary>
  382. /// Parses a decimal from a protobuf stream
  383. /// </summary>
  384. public static decimal ReadDecimal(ProtoReader reader)
  385. {
  386. ulong low = 0;
  387. uint high = 0;
  388. uint signScale = 0;
  389. int fieldNumber;
  390. SubItemToken token = ProtoReader.StartSubItem(reader);
  391. while ((fieldNumber = reader.ReadFieldHeader()) > 0)
  392. {
  393. switch (fieldNumber)
  394. {
  395. case FieldDecimalLow: low = reader.ReadUInt64(); break;
  396. case FieldDecimalHigh: high = reader.ReadUInt32(); break;
  397. case FieldDecimalSignScale: signScale = reader.ReadUInt32(); break;
  398. default: reader.SkipField(); break;
  399. }
  400. }
  401. ProtoReader.EndSubItem(token, reader);
  402. if (low == 0 && high == 0) return decimal.Zero;
  403. int lo = (int)(low & 0xFFFFFFFFL),
  404. mid = (int)((low >> 32) & 0xFFFFFFFFL),
  405. hi = (int)high;
  406. bool isNeg = (signScale & 0x0001) == 0x0001;
  407. byte scale = (byte)((signScale & 0x01FE) >> 1);
  408. return new decimal(lo, mid, hi, isNeg, scale);
  409. }
  410. /// <summary>
  411. /// Writes a decimal to a protobuf stream
  412. /// </summary>
  413. public static void WriteDecimal(decimal value, ProtoWriter writer)
  414. {
  415. int[] bits = decimal.GetBits(value);
  416. ulong a = ((ulong)bits[1]) << 32, b = ((ulong)bits[0]) & 0xFFFFFFFFL;
  417. ulong low = a | b;
  418. uint high = (uint)bits[2];
  419. uint signScale = (uint)(((bits[3] >> 15) & 0x01FE) | ((bits[3] >> 31) & 0x0001));
  420. SubItemToken token = ProtoWriter.StartSubItem(null, writer);
  421. if (low != 0) {
  422. ProtoWriter.WriteFieldHeader(FieldDecimalLow, WireType.Variant, writer);
  423. ProtoWriter.WriteUInt64(low, writer);
  424. }
  425. if (high != 0)
  426. {
  427. ProtoWriter.WriteFieldHeader(FieldDecimalHigh, WireType.Variant, writer);
  428. ProtoWriter.WriteUInt32(high, writer);
  429. }
  430. if (signScale != 0)
  431. {
  432. ProtoWriter.WriteFieldHeader(FieldDecimalSignScale, WireType.Variant, writer);
  433. ProtoWriter.WriteUInt32(signScale, writer);
  434. }
  435. ProtoWriter.EndSubItem(token, writer);
  436. }
  437. const int FieldGuidLow = 1, FieldGuidHigh = 2;
  438. /// <summary>
  439. /// Writes a Guid to a protobuf stream
  440. /// </summary>
  441. public static void WriteGuid(Guid value, ProtoWriter dest)
  442. {
  443. byte[] blob = value.ToByteArray();
  444. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  445. if (value != Guid.Empty)
  446. {
  447. ProtoWriter.WriteFieldHeader(FieldGuidLow, WireType.Fixed64, dest);
  448. ProtoWriter.WriteBytes(blob, 0, 8, dest);
  449. ProtoWriter.WriteFieldHeader(FieldGuidHigh, WireType.Fixed64, dest);
  450. ProtoWriter.WriteBytes(blob, 8, 8, dest);
  451. }
  452. ProtoWriter.EndSubItem(token, dest);
  453. }
  454. /// <summary>
  455. /// Parses a Guid from a protobuf stream
  456. /// </summary>
  457. public static Guid ReadGuid(ProtoReader source)
  458. {
  459. ulong low = 0, high = 0;
  460. int fieldNumber;
  461. SubItemToken token = ProtoReader.StartSubItem(source);
  462. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  463. {
  464. switch (fieldNumber)
  465. {
  466. case FieldGuidLow: low = source.ReadUInt64(); break;
  467. case FieldGuidHigh: high = source.ReadUInt64(); break;
  468. default: source.SkipField(); break;
  469. }
  470. }
  471. ProtoReader.EndSubItem(token, source);
  472. if(low == 0 && high == 0) return Guid.Empty;
  473. uint a = (uint)(low >> 32), b = (uint)low, c = (uint)(high >> 32), d= (uint)high;
  474. return new Guid((int)b, (short)a, (short)(a >> 16),
  475. (byte)d, (byte)(d >> 8), (byte)(d >> 16), (byte)(d >> 24),
  476. (byte)c, (byte)(c >> 8), (byte)(c >> 16), (byte)(c >> 24));
  477. }
  478. private const int
  479. FieldExistingObjectKey = 1,
  480. FieldNewObjectKey = 2,
  481. FieldExistingTypeKey = 3,
  482. FieldNewTypeKey = 4,
  483. FieldTypeName = 8,
  484. FieldObject = 10;
  485. /// <summary>
  486. /// Optional behaviours that introduce .NET-specific functionality
  487. /// </summary>
  488. [Flags]
  489. public enum NetObjectOptions : byte
  490. {
  491. /// <summary>
  492. /// No special behaviour
  493. /// </summary>
  494. None = 0,
  495. /// <summary>
  496. /// Enables full object-tracking/full-graph support.
  497. /// </summary>
  498. AsReference = 1,
  499. /// <summary>
  500. /// Embeds the type information into the stream, allowing usage with types not known in advance.
  501. /// </summary>
  502. DynamicType = 2,
  503. /// <summary>
  504. /// If false, the constructor for the type is bypassed during deserialization, meaning any field initializers
  505. /// or other initialization code is skipped.
  506. /// </summary>
  507. UseConstructor = 4,
  508. /// <summary>
  509. /// Should the object index be reserved, rather than creating an object promptly
  510. /// </summary>
  511. LateSet = 8
  512. }
  513. /// <summary>
  514. /// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
  515. /// </summary>
  516. public static object ReadNetObject(object value, ProtoReader source, int key, Type type, NetObjectOptions options)
  517. {
  518. #if FEAT_IKVM
  519. throw new NotSupportedException();
  520. #else
  521. SubItemToken token = ProtoReader.StartSubItem(source);
  522. int fieldNumber;
  523. int newObjectKey = -1, newTypeKey = -1, tmp;
  524. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  525. {
  526. switch (fieldNumber)
  527. {
  528. case FieldExistingObjectKey:
  529. tmp = source.ReadInt32();
  530. value = source.NetCache.GetKeyedObject(tmp);
  531. break;
  532. case FieldNewObjectKey:
  533. newObjectKey = source.ReadInt32();
  534. break;
  535. case FieldExistingTypeKey:
  536. tmp = source.ReadInt32();
  537. type = (Type)source.NetCache.GetKeyedObject(tmp);
  538. key = source.GetTypeKey(ref type);
  539. break;
  540. case FieldNewTypeKey:
  541. newTypeKey = source.ReadInt32();
  542. break;
  543. case FieldTypeName:
  544. string typeName = source.ReadString();
  545. type = source.DeserializeType(typeName);
  546. if(type == null)
  547. {
  548. throw new ProtoException("Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)");
  549. }
  550. if (type == typeof(string))
  551. {
  552. key = -1;
  553. }
  554. else
  555. {
  556. key = source.GetTypeKey(ref type);
  557. if (key < 0)
  558. throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
  559. }
  560. break;
  561. case FieldObject:
  562. bool isString = type == typeof(string);
  563. bool wasNull = value == null;
  564. bool lateSet = wasNull && (isString || ((options & NetObjectOptions.LateSet) != 0));
  565. if (newObjectKey >= 0 && !lateSet)
  566. {
  567. if (value == null)
  568. {
  569. source.TrapNextObject(newObjectKey);
  570. }
  571. else
  572. {
  573. source.NetCache.SetKeyedObject(newObjectKey, value);
  574. }
  575. if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
  576. }
  577. object oldValue = value;
  578. if (isString)
  579. {
  580. value = source.ReadString();
  581. }
  582. else
  583. {
  584. value = ProtoReader.ReadTypedObject(oldValue, key, source, type);
  585. }
  586. if (newObjectKey >= 0)
  587. {
  588. if(wasNull && !lateSet)
  589. { // this both ensures (via exception) that it *was* set, and makes sure we don't shout
  590. // about changed references
  591. oldValue = source.NetCache.GetKeyedObject(newObjectKey);
  592. }
  593. if (lateSet)
  594. {
  595. source.NetCache.SetKeyedObject(newObjectKey, value);
  596. if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
  597. }
  598. }
  599. if (newObjectKey >= 0 && !lateSet && !ReferenceEquals(oldValue, value))
  600. {
  601. throw new ProtoException("A reference-tracked object changed reference during deserialization");
  602. }
  603. if (newObjectKey < 0 && newTypeKey >= 0)
  604. { // have a new type, but not a new object
  605. source.NetCache.SetKeyedObject(newTypeKey, type);
  606. }
  607. break;
  608. default:
  609. source.SkipField();
  610. break;
  611. }
  612. }
  613. if(newObjectKey >= 0 && (options & NetObjectOptions.AsReference) == 0)
  614. {
  615. throw new ProtoException("Object key in input stream, but reference-tracking was not expected");
  616. }
  617. ProtoReader.EndSubItem(token, source);
  618. return value;
  619. #endif
  620. }
  621. /// <summary>
  622. /// Writes an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
  623. /// </summary>
  624. public static void WriteNetObject(object value, ProtoWriter dest, int key, NetObjectOptions options)
  625. {
  626. #if FEAT_IKVM
  627. throw new NotSupportedException();
  628. #else
  629. if (dest == null) throw new ArgumentNullException("dest");
  630. bool dynamicType = (options & NetObjectOptions.DynamicType) != 0,
  631. asReference = (options & NetObjectOptions.AsReference) != 0;
  632. WireType wireType = dest.WireType;
  633. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  634. bool writeObject = true;
  635. if (asReference)
  636. {
  637. bool existing;
  638. int objectKey = dest.NetCache.AddObjectKey(value, out existing);
  639. ProtoWriter.WriteFieldHeader(existing ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest);
  640. ProtoWriter.WriteInt32(objectKey, dest);
  641. if (existing)
  642. {
  643. writeObject = false;
  644. }
  645. }
  646. if (writeObject)
  647. {
  648. if (dynamicType)
  649. {
  650. bool existing;
  651. Type type = value.GetType();
  652. if (!(value is string))
  653. {
  654. key = dest.GetTypeKey(ref type);
  655. if (key < 0) throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
  656. }
  657. int typeKey = dest.NetCache.AddObjectKey(type, out existing);
  658. ProtoWriter.WriteFieldHeader(existing ? FieldExistingTypeKey : FieldNewTypeKey, WireType.Variant, dest);
  659. ProtoWriter.WriteInt32(typeKey, dest);
  660. if (!existing)
  661. {
  662. ProtoWriter.WriteFieldHeader(FieldTypeName, WireType.String, dest);
  663. ProtoWriter.WriteString(dest.SerializeType(type), dest);
  664. }
  665. }
  666. ProtoWriter.WriteFieldHeader(FieldObject, wireType, dest);
  667. if (value is string)
  668. {
  669. ProtoWriter.WriteString((string)value, dest);
  670. }
  671. else {
  672. ProtoWriter.WriteObject(value, key, dest);
  673. }
  674. }
  675. ProtoWriter.EndSubItem(token, dest);
  676. #endif
  677. }
  678. }
  679. }