Extensible.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. using System;
  2. using System.Collections.Generic;
  3. using ProtoBuf.Meta;
  4. using System.Collections;
  5. namespace ProtoBuf
  6. {
  7. /// <summary>
  8. /// Simple base class for supporting unexpected fields allowing
  9. /// for loss-less round-tips/merge, even if the data is not understod.
  10. /// The additional fields are (by default) stored in-memory in a buffer.
  11. /// </summary>
  12. /// <remarks>As an example of an alternative implementation, you might
  13. /// choose to use the file system (temporary files) as the back-end, tracking
  14. /// only the paths [such an object would ideally be IDisposable and use
  15. /// a finalizer to ensure that the files are removed].</remarks>
  16. /// <seealso cref="IExtensible"/>
  17. public abstract class Extensible : IExtensible
  18. {
  19. // note: not marked ProtoContract - no local state, and can't
  20. // predict sub-classes
  21. private IExtension extensionObject;
  22. IExtension IExtensible.GetExtensionObject(bool createIfMissing)
  23. {
  24. return GetExtensionObject(createIfMissing);
  25. }
  26. /// <summary>
  27. /// Retrieves the <see cref="IExtension">extension</see> object for the current
  28. /// instance, optionally creating it if it does not already exist.
  29. /// </summary>
  30. /// <param name="createIfMissing">Should a new extension object be
  31. /// created if it does not already exist?</param>
  32. /// <returns>The extension object if it exists (or was created), or null
  33. /// if the extension object does not exist or is not available.</returns>
  34. /// <remarks>The <c>createIfMissing</c> argument is false during serialization,
  35. /// and true during deserialization upon encountering unexpected fields.</remarks>
  36. protected virtual IExtension GetExtensionObject(bool createIfMissing)
  37. {
  38. return GetExtensionObject(ref extensionObject, createIfMissing);
  39. }
  40. /// <summary>
  41. /// Provides a simple, default implementation for <see cref="IExtension">extension</see> support,
  42. /// optionally creating it if it does not already exist. Designed to be called by
  43. /// classes implementing <see cref="IExtensible"/>.
  44. /// </summary>
  45. /// <param name="createIfMissing">Should a new extension object be
  46. /// created if it does not already exist?</param>
  47. /// <param name="extensionObject">The extension field to check (and possibly update).</param>
  48. /// <returns>The extension object if it exists (or was created), or null
  49. /// if the extension object does not exist or is not available.</returns>
  50. /// <remarks>The <c>createIfMissing</c> argument is false during serialization,
  51. /// and true during deserialization upon encountering unexpected fields.</remarks>
  52. public static IExtension GetExtensionObject(ref IExtension extensionObject, bool createIfMissing)
  53. {
  54. if (createIfMissing && extensionObject == null)
  55. {
  56. extensionObject = new BufferExtension();
  57. }
  58. return extensionObject;
  59. }
  60. #if !NO_RUNTIME
  61. /// <summary>
  62. /// Appends the value as an additional (unexpected) data-field for the instance.
  63. /// Note that for non-repeated sub-objects, this equates to a merge operation;
  64. /// for repeated sub-objects this adds a new instance to the set; for simple
  65. /// values the new value supercedes the old value.
  66. /// </summary>
  67. /// <remarks>Note that appending a value does not remove the old value from
  68. /// the stream; avoid repeatedly appending values for the same field.</remarks>
  69. /// <typeparam name="TValue">The type of the value to append.</typeparam>
  70. /// <param name="instance">The extensible object to append the value to.</param>
  71. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  72. /// <param name="value">The value to append.</param>
  73. public static void AppendValue<TValue>(IExtensible instance, int tag, TValue value)
  74. {
  75. AppendValue<TValue>(instance, tag, DataFormat.Default, value);
  76. }
  77. /// <summary>
  78. /// Appends the value as an additional (unexpected) data-field for the instance.
  79. /// Note that for non-repeated sub-objects, this equates to a merge operation;
  80. /// for repeated sub-objects this adds a new instance to the set; for simple
  81. /// values the new value supercedes the old value.
  82. /// </summary>
  83. /// <remarks>Note that appending a value does not remove the old value from
  84. /// the stream; avoid repeatedly appending values for the same field.</remarks>
  85. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  86. /// <param name="format">The data-format to use when encoding the value.</param>
  87. /// <param name="instance">The extensible object to append the value to.</param>
  88. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  89. /// <param name="value">The value to append.</param>
  90. public static void AppendValue<TValue>(IExtensible instance, int tag, DataFormat format, TValue value)
  91. {
  92. ExtensibleUtil.AppendExtendValue(RuntimeTypeModel.Default, instance, tag, format, value);
  93. }
  94. /// <summary>
  95. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  96. /// The value returned is the composed value after merging any duplicated content; if the
  97. /// value is "repeated" (a list), then use GetValues instead.
  98. /// </summary>
  99. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  100. /// <param name="instance">The extensible object to obtain the value from.</param>
  101. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  102. /// <returns>The effective value of the field, or the default value if not found.</returns>
  103. public static TValue GetValue<TValue>(IExtensible instance, int tag)
  104. {
  105. return GetValue<TValue>(instance, tag, DataFormat.Default);
  106. }
  107. /// <summary>
  108. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  109. /// The value returned is the composed value after merging any duplicated content; if the
  110. /// value is "repeated" (a list), then use GetValues instead.
  111. /// </summary>
  112. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  113. /// <param name="instance">The extensible object to obtain the value from.</param>
  114. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  115. /// <param name="format">The data-format to use when decoding the value.</param>
  116. /// <returns>The effective value of the field, or the default value if not found.</returns>
  117. public static TValue GetValue<TValue>(IExtensible instance, int tag, DataFormat format)
  118. {
  119. TryGetValue<TValue>(instance, tag, format, out TValue value);
  120. return value;
  121. }
  122. /// <summary>
  123. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  124. /// The value returned (in "value") is the composed value after merging any duplicated content;
  125. /// if the value is "repeated" (a list), then use GetValues instead.
  126. /// </summary>
  127. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  128. /// <param name="value">The effective value of the field, or the default value if not found.</param>
  129. /// <param name="instance">The extensible object to obtain the value from.</param>
  130. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  131. /// <returns>True if data for the field was present, false otherwise.</returns>
  132. public static bool TryGetValue<TValue>(IExtensible instance, int tag, out TValue value)
  133. {
  134. return TryGetValue<TValue>(instance, tag, DataFormat.Default, out value);
  135. }
  136. /// <summary>
  137. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  138. /// The value returned (in "value") is the composed value after merging any duplicated content;
  139. /// if the value is "repeated" (a list), then use GetValues instead.
  140. /// </summary>
  141. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  142. /// <param name="value">The effective value of the field, or the default value if not found.</param>
  143. /// <param name="instance">The extensible object to obtain the value from.</param>
  144. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  145. /// <param name="format">The data-format to use when decoding the value.</param>
  146. /// <returns>True if data for the field was present, false otherwise.</returns>
  147. public static bool TryGetValue<TValue>(IExtensible instance, int tag, DataFormat format, out TValue value)
  148. {
  149. return TryGetValue<TValue>(instance, tag, format, false, out value);
  150. }
  151. /// <summary>
  152. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  153. /// The value returned (in "value") is the composed value after merging any duplicated content;
  154. /// if the value is "repeated" (a list), then use GetValues instead.
  155. /// </summary>
  156. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  157. /// <param name="value">The effective value of the field, or the default value if not found.</param>
  158. /// <param name="instance">The extensible object to obtain the value from.</param>
  159. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  160. /// <param name="format">The data-format to use when decoding the value.</param>
  161. /// <param name="allowDefinedTag">Allow tags that are present as part of the definition; for example, to query unknown enum values.</param>
  162. /// <returns>True if data for the field was present, false otherwise.</returns>
  163. public static bool TryGetValue<TValue>(IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out TValue value)
  164. {
  165. value = default;
  166. bool set = false;
  167. foreach (TValue val in ExtensibleUtil.GetExtendedValues<TValue>(instance, tag, format, true, allowDefinedTag))
  168. {
  169. // expecting at most one yield...
  170. // but don't break; need to read entire stream
  171. value = val;
  172. set = true;
  173. }
  174. return set;
  175. }
  176. /// <summary>
  177. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  178. /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
  179. /// (list) fields.
  180. /// </summary>
  181. /// <remarks>The extended data is processed lazily as the enumerator is iterated.</remarks>
  182. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  183. /// <param name="instance">The extensible object to obtain the value from.</param>
  184. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  185. /// <returns>An enumerator that yields each occurrence of the field.</returns>
  186. public static IEnumerable<TValue> GetValues<TValue>(IExtensible instance, int tag)
  187. {
  188. return ExtensibleUtil.GetExtendedValues<TValue>(instance, tag, DataFormat.Default, false, false);
  189. }
  190. /// <summary>
  191. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  192. /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
  193. /// (list) fields.
  194. /// </summary>
  195. /// <remarks>The extended data is processed lazily as the enumerator is iterated.</remarks>
  196. /// <typeparam name="TValue">The data-type of the field.</typeparam>
  197. /// <param name="instance">The extensible object to obtain the value from.</param>
  198. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  199. /// <param name="format">The data-format to use when decoding the value.</param>
  200. /// <returns>An enumerator that yields each occurrence of the field.</returns>
  201. public static IEnumerable<TValue> GetValues<TValue>(IExtensible instance, int tag, DataFormat format)
  202. {
  203. return ExtensibleUtil.GetExtendedValues<TValue>(instance, tag, format, false, false);
  204. }
  205. #endif
  206. /// <summary>
  207. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  208. /// The value returned (in "value") is the composed value after merging any duplicated content;
  209. /// if the value is "repeated" (a list), then use GetValues instead.
  210. /// </summary>
  211. /// <param name="type">The data-type of the field.</param>
  212. /// <param name="model">The model to use for configuration.</param>
  213. /// <param name="value">The effective value of the field, or the default value if not found.</param>
  214. /// <param name="instance">The extensible object to obtain the value from.</param>
  215. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  216. /// <param name="format">The data-format to use when decoding the value.</param>
  217. /// <param name="allowDefinedTag">Allow tags that are present as part of the definition; for example, to query unknown enum values.</param>
  218. /// <returns>True if data for the field was present, false otherwise.</returns>
  219. public static bool TryGetValue(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out object value)
  220. {
  221. value = null;
  222. bool set = false;
  223. foreach (object val in ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, true, allowDefinedTag))
  224. {
  225. // expecting at most one yield...
  226. // but don't break; need to read entire stream
  227. value = val;
  228. set = true;
  229. }
  230. return set;
  231. }
  232. /// <summary>
  233. /// Queries an extensible object for an additional (unexpected) data-field for the instance.
  234. /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
  235. /// (list) fields.
  236. /// </summary>
  237. /// <remarks>The extended data is processed lazily as the enumerator is iterated.</remarks>
  238. /// <param name="model">The model to use for configuration.</param>
  239. /// <param name="type">The data-type of the field.</param>
  240. /// <param name="instance">The extensible object to obtain the value from.</param>
  241. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  242. /// <param name="format">The data-format to use when decoding the value.</param>
  243. /// <returns>An enumerator that yields each occurrence of the field.</returns>
  244. public static IEnumerable GetValues(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format)
  245. {
  246. return ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, false, false);
  247. }
  248. /// <summary>
  249. /// Appends the value as an additional (unexpected) data-field for the instance.
  250. /// Note that for non-repeated sub-objects, this equates to a merge operation;
  251. /// for repeated sub-objects this adds a new instance to the set; for simple
  252. /// values the new value supercedes the old value.
  253. /// </summary>
  254. /// <remarks>Note that appending a value does not remove the old value from
  255. /// the stream; avoid repeatedly appending values for the same field.</remarks>
  256. /// <param name="model">The model to use for configuration.</param>
  257. /// <param name="format">The data-format to use when encoding the value.</param>
  258. /// <param name="instance">The extensible object to append the value to.</param>
  259. /// <param name="tag">The field identifier; the tag should not be defined as a known data-field for the instance.</param>
  260. /// <param name="value">The value to append.</param>
  261. public static void AppendValue(TypeModel model, IExtensible instance, int tag, DataFormat format, object value)
  262. {
  263. ExtensibleUtil.AppendExtendValue(model, instance, tag, format, value);
  264. }
  265. }
  266. }