Extensible.cs 17 KB

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