-
Notifications
You must be signed in to change notification settings - Fork 757
Expand file tree
/
Copy pathFastSerialization.cs
More file actions
2587 lines (2437 loc) · 110 KB
/
FastSerialization.cs
File metadata and controls
2587 lines (2437 loc) · 110 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation. All rights reserved.
/* This file is best viewed using outline mode (Ctrl-M Ctrl-O) */
// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
// It is available from http://www.codeplex.com/hyperAddin
/* If you uncomment this line, log.serialize.xml and log.deserialize.xml are created, which allow debugging */
// #define DEBUG_SERIALIZE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text; // For StringBuilder.
// see #Introduction and #SerializerIntroduction
namespace FastSerialization
{
// #Introduction
//
// Sadly, System.Runtime.Serialization has a serious performance flaw. In the scheme created there, the
// basic contract between an object and the serializer is fundamentally heavy. For serialization the
// contract is for the object to implement System.Runtime.Serialization.ISerializable.GetObjectData
// and this should a series of AddValue() APIs on System.Runtime.Serialization.SerializationInfo
// which are given field names and values. The AddValue APIs box the values and place them in a table, It
// is then the serializers job to actually send out the bits given this table. The REQUIRED work of
// serializing an integers copying 4 bytes to some output buffer (a few instructions), however the
// protocol above requires 1000s.
//
// The classes in Serialize.cs are an attempt to create really light weight serialization. At the heart
// of the design are two interfaces IStreamReader and IStreamWriter. They are a simplified
// INTERFACE much like System.IO.BinaryReader and System.IO.BinaryWriter, that know how to
// write only the most common data types (integers and strings). They also fundamentally understand that
// they are a stream of bytes, and thus have the concept of StreamLabel which is a 'pointer' to a
// spot in the stream. This is critically important as it allows the serialized form to create a
// complicated graph of objects in the stream. While IStreamWriter does not have the ability to seek,
// the IStreamReader does (using StreamLabel), because it is expected that the reader will want
// to follow StreamLabel 'pointers' to traverse the serialized data in a more random access way.
//
// However, in general, an object needs more than a MemoryStreamWriter to serialize itself. When an object
// graph could have cycles, it needs a way of remembering which objects it has already serialized. It
// also needs a way encoding types, because in general the type of an object cannot always be inferred
// from its context. This is the job of the Serializer class. A Serializer holds all the state
// needed to represent a partially serialized object graph, but the most important part of a
// Serializer is its Serializer.writer property, which holds the logical output stream.
//
// Similarly a Deserializer holds all the 'in flight' information needed to deserialize a complete
// object graph, and its most important property is its Deserializer.reader that holds the logical
// input stream.
//
// An object becomes serializable by doing two things
// * implementing the IFastSerializable interface and implementing the
// IFastSerializable.ToStream and IFastSerializable.FromStream methods.
// * implementing a public constructor with no arguments (default constructor). This is needed because
// an object needs to be created before IFastSerializable.FromStream can be called.
//
// The IFastSerializable.ToStream method that the object implements is passed a Serializer, and
// the object is free to take advantage of all the facilities (like its serialized object table) to help
// serialize itself, however at its heart, the ToStream method tends to fetch the Serialier.writer
// and write out the primitive fields in order. Similarly at the heart of the
// IFastSerializable.FromStream method is fetching the Deserializer.reader and reading in a
// series of primitive types.
//
// Now the basic overhead of serializing a object in the common case is
//
// * A interface call to IFastSerializable.ToStream.
// * A fetch of IStreamWriter from the Serialier.writer field
// * a series of IStreamWriter.Write operations which is an interface call, plus the logic to
// store the actual data to the stream (the real work).
//
// This is MUCH leaner, and now dominated by actual work of copying the data to the output buffer.
/// <summary>
/// Allows users of serialization and de-serialization mechanisms to specify the size of the StreamLabel.
/// As traces get larger, there is a need to support larger file sizes, and thus to increase the addressable
/// space within the files. StreamLabel instances are 8-bytes in-memory, but all serialization and de-serialization
/// of them results in the upper 4-bytes being lost. This setting will allow Serializer and Deserializer to read
/// and write 8-byte StreamLabel instances.
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
enum StreamLabelWidth
{
FourBytes = 0,
EightBytes = 1
};
/// <summary>
/// Allows users of serialization and de-serialization mechanism to specify the alignment required by the
/// reader.
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
enum StreamReaderAlignment : int
{
OneByte = 1,
FourBytes = 4,
EightBytes = 8
};
/// <summary>
/// Settings used by <code>Serializer</code> and <code>Deserializer</code>.
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
sealed class SerializationSettings
{
internal StreamLabelWidth StreamLabelWidth { get; }
internal StreamReaderAlignment StreamReaderAlignment { get; }
public static SerializationSettings Default { get; } = new SerializationSettings(
StreamLabelWidth.EightBytes,
StreamReaderAlignment.EightBytes);
public SerializationSettings WithStreamLabelWidth(StreamLabelWidth width)
{
return new SerializationSettings(
width,
StreamReaderAlignment);
}
public SerializationSettings WithStreamReaderAlignment(StreamReaderAlignment alignment)
{
return new SerializationSettings(
StreamLabelWidth,
alignment);
}
private SerializationSettings(
StreamLabelWidth streamLabelWidth, StreamReaderAlignment streamReaderAlignment)
{
StreamLabelWidth = streamLabelWidth;
StreamReaderAlignment = streamReaderAlignment;
}
}
/// <summary>
/// A StreamLabel represents a position in a IStreamReader or IStreamWriter.
/// In memory it is represented as a 64 bit signed value but to preserve compat
/// with the FastSerializer.1 format it is a 32 bit unsigned value when
/// serialized in a file. FastSerializer can parse files exceeding 32 bit sizes
/// as long as the format doesn't persist a StreamLabel in the content. NetTrace
/// is an example of this.
/// During writing it is generated by the IStreamWriter.GetLabel method an
/// consumed by the IStreamWriter.WriteLabel method. On reading you can use
/// IStreamReader.Current and and IStreamReader.
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
enum StreamLabel : long
{
/// <summary>
/// Represents a stream label that is not a valid value
/// </summary>
Invalid = (long)-1
};
/// <summary>
/// IStreamWriter is meant to be a very simple streaming protocol. You can write integral types,
/// strings, and labels to the stream itself.
///
/// IStreamWrite can be thought of a simplified System.IO.BinaryWriter, or maybe the writer
/// part of a System.IO.Stream with a few helpers for primitive types.
///
/// See also IStreamReader
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
interface IStreamWriter : IDisposable
{
/// <summary>
/// Write a byte to a stream
/// </summary>
void Write(byte value);
/// <summary>
/// Write a short to a stream
/// </summary>
void Write(short value);
/// <summary>
/// Write an int to a stream
/// </summary>
void Write(int value);
/// <summary>
/// Write a long to a stream
/// </summary>
void Write(long value);
/// <summary>
/// Write a StreamLabel (a pointer to another part of the stream) to a stream
/// </summary>
void Write(StreamLabel value);
/// <summary>
/// Write a string to a stream (supports null values).
/// </summary>
void Write(string value);
/// <summary>
/// Get the stream label for the current position (points at whatever is written next
/// </summary>
/// <returns></returns>
StreamLabel GetLabel();
/// <summary>
/// Write a SuffixLabel it must be the last thing written to the stream. The stream
/// guarantees that this value can be efficiently read at any time (probably by seeking
/// back from the end of the stream)). The idea is that when you generate a 'tableOfContents'
/// you can only do this after processing the data (and probably writing it out), If you
/// remember where you write this table of contents and then write a suffix label to it
/// as the last thing in the stream using this API, you guarantee that the reader can
/// efficiently seek to the end, read the value, and then goto that position. (See
/// IStreamReader.GotoSuffixLabel for more)
/// </summary>
void WriteSuffixLabel(StreamLabel value);
/// <summary>
/// The settings associated with this writer.
/// </summary>
SerializationSettings Settings { get; }
}
/// IStreamReader is meant to be a very simple streaming protocol. You can read integral types,
/// strings, and labels to the stream itself. You can also goto labels you have read from the stream.
///
/// IStreamReader can be thought of a simplified System.IO.BinaryReder, or maybe the reader
/// part of a System.IO.Stream with a few helpers for primitive types.
///
/// See also IStreamWriter
#if FASTSERIALIZATION_PUBLIC
public
#endif
interface IStreamReader : IDisposable
{
/// <summary>
/// Read a byte from the stream
/// </summary>
byte ReadByte();
/// <summary>
/// Read a short from the stream
/// </summary>
short ReadInt16();
/// <summary>
/// Read an int from the stream
/// </summary>
int ReadInt32();
/// <summary>
/// Read a long from the stream
/// </summary>
long ReadInt64();
/// <summary>
/// Read a string from the stream. Can represent null strings
/// </summary>
string ReadString();
/// <summary>
/// Read a span of bytes from the stream.
/// </summary>
void Read(byte[] data, int offset, int length);
/// <summary>
/// Read a StreamLabel (pointer to some other part of the stream) from the stream
/// </summary>
StreamLabel ReadLabel();
/// <summary>
/// Goto a location in the stream
/// </summary>
void Goto(StreamLabel label);
/// <summary>
/// Returns the current position in the stream.
/// </summary>
StreamLabel Current { get; }
/// <summary>
/// Sometimes information is only known after writing the entire stream. This information can be put
/// on the end of the stream, but there needs to be a way of finding it relative to the end, rather
/// than from the beginning. A IStreamReader, however, does not actually let you go 'backwards' easily
/// because it does not guarantee the size what it writes out (it might compress).
///
/// The solution is the concept of a 'suffixLabel' which is location in the stream where you can always
/// efficiently get to.
///
/// It is written with a special API (WriteSuffixLabel that must be the last thing written. It is
/// expected that it simply write an uncompressed StreamLabel. It can then be used by using the
/// GotoSTreamLabel() method below. This goes to this well know position in the stream. We expect
/// this is implemented by seeking to the end of the stream, reading the uncompressed streamLabel,
/// and then seeking to that position.
/// </summary>
void GotoSuffixLabel();
/// <summary>
/// The settings associated with this reader.
/// </summary>
SerializationSettings Settings { get; }
}
#if !DOTNET_V35
/// <summary>
/// Support for higher level operations on IStreamWriter and IStreamReader
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
static class IStreamWriterExentions
{
/// <summary>
/// Writes a Guid to stream 'writer' as sequence of 8 bytes
/// </summary>
public static void Write(this IStreamWriter writer, Guid guid)
{
byte[] bytes = guid.ToByteArray();
for (int i = 0; i < bytes.Length; i++)
{
writer.Write(bytes[i]);
}
}
/// <summary>
/// Reads a Guid to stream 'reader' as sequence of 8 bytes and returns it
/// </summary>
public static Guid ReadGuid(this IStreamReader reader)
{
byte[] bytes = new byte[16];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = reader.ReadByte();
}
return new Guid(bytes);
}
public static string ReadNullTerminatedUnicodeString(this IStreamReader reader, StringBuilder sb = null)
{
if (sb == null)
{
sb = new StringBuilder();
}
short value = reader.ReadInt16();
while (value != 0)
{
sb.Append(Convert.ToChar(value));
value = reader.ReadInt16();
}
return sb.ToString();
}
/// <summary>
/// Returns a StreamLabel that is the sum of label + offset.
/// </summary>
public static StreamLabel Add(this StreamLabel label, int offset)
{
return (StreamLabel)((long)label + offset);
}
/// <summary>
/// Returns the difference between two stream labels
/// </summary>
public static long Sub(this StreamLabel label, StreamLabel other)
{
return (long)label - (long)other;
}
/// <summary>
/// Convenience method for skipping a a certain number of bytes in the stream.
/// </summary>
public static void Skip(this IStreamReader reader, int byteCount)
{
reader.Goto((StreamLabel)((long)reader.Current + byteCount));
}
}
#endif
/// <summary>
/// Like a StreamLabel, a ForwardReference represents a pointer to a location in the stream.
/// However unlike a StreamLabel, the exact value in the stream does not need to be known at the
/// time the forward references is written. Instead the ID is written, and later that ID is
/// associated with the target location (using DefineForwardReference).
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
enum ForwardReference : int
{
/// <summary>
/// Returned when no appropriate ForwardReference exists.
/// </summary>
Invalid = -1
};
/// <summary>
/// #SerializerIntroduction see also #StreamLayout
///
/// The Serializer class is a general purpose object graph serializer helper. While it does not have
/// any knowledge of the serialization format of individual object, it does impose conventions on how to
/// serialize support information like the header (which holds versioning information), a trailer (which
/// holds deferred pointer information), and how types are versioned. However these conventions are
/// intended to be very generic and thus this class can be used for essentially any serialization need.
///
/// Goals:
/// * Allows full range of serialization, including subclassing and cyclic object graphs.
/// * Can be serialized and deserialized efficiently sequentially (no seeks MANDATED on read or
/// write). This allows the serializer to be used over pipes and other non-seekable devices).
/// * Pay for play (thus very efficient in simple cases (no subclassing or cyclic graphs).
/// * Ideally self-describing, and debuggable (output as XML if desired?)
///
/// Versioning:
/// * We want the ability for new formats to accept old versions if objects wish to support old
/// formats
/// * Also wish to allow new formats to be read by OLD version if the new format is just an
/// 'extension' (data added to end of objects). This makes making new versions almost pain-free.
///
/// Concepts:
/// * No-seek requirement
///
/// The serialized form should be such that it can be deserialized efficiently in a serial fashion
/// (no seeks). This means all information needed to deserialize has to be 'just in time' (can't
/// be some table at the end). Pragmatically this means that type information (needed to create
/// instances), has to be output on first use, so it is available for the deserializer.
///
/// * Laziness requirement
///
/// While is should be possible to read the serialized for sequentially, we should also not force
/// it. It should be possible to have a large file that represents a persisted structure that can
/// be lazily brought into memory on demand. This means that all information needed to
/// deserialize must also be 'randomly available' and not depend on reading from the beginning.
/// Pragmatically this means that type information, and forward forwardReference information needs to
/// have a table in a well known Location at the end so that it can be found without having to
/// search the file sequentially.
///
/// * Versioning requirement
///
/// To allow OLD code to access NEW formats, it must be the case that the serialized form of
/// every instance knows how to 'skip' past any new data (even if it does not know its exact
/// size). To support this, objects have 'begin' and 'end' tags, which allows the deserializer to
/// skip the next object.
///
/// * Polymorphism requirement
///
/// Because the user of a filed may not know the exact instance stored there, in general objects
/// need to store the exact type of the instance. Thus they need to store a type identifier, this
/// can be folded into the 'begin' tag.
///
/// * Arbitrary object graph (circularity) requirement (Forward references)
///
/// The serializer needs to be able to serialize arbitrary object graphs, including those with
/// cycles in them. While you can do this without forward references, the system is more flexible
/// if it has the concept of a forward reference. Thus whenever a object reference is required, a
/// 'forward forwardReference' can be given instead. What gets serialized is simply an unique forward
/// reference index (index into an array), and at some later time that index is given its true
/// value. This can either happen with the target object is serialized (see
/// Serializer.Tags.ForwardDefintion) or at the end of the serialization in a forward
/// reference table (which allows forward references to be resolved without scanning then entire
/// file.
///
/// * Contract between objects IFastSerializable.ToStream:
///
/// The heart of the serialization and deserialization process the IFastSerializable
/// interface, which implements just two methods: ToStream (for serializing an object), and
/// FromStream (for deserializing and object). This interfaces is the mechanism by which objects
/// tell the serializer what data to store for an individual instance. However this core is not
/// enough. An object that implements IFastSerializable must also implement a default
/// constructor (constructor with no args), so that that deserializer can create the object (and
/// then call FromStream to populated it).
///
/// The ToStream method is only responsible for serializing the data in the object, and by itself
/// is not sufficient to serialize an interconnected, polymorphic graph of objects. It needs
/// help from the Serializer and Deserialize to do this. Serializer takes on the
/// responsibility to deal with persisting type information (so that Deserialize can create
/// the correct type before IFastSerializable.FromStream is called). It is also the
/// serializer's responsibility to provide the mechanism for dealing with circular object graphs
/// and forward references.
///
/// * Layout of a serialized object: A serialized object has the following basic format
///
/// * If the object is the definition of a previous forward references, then the definition must
/// begin with a Serializer.Tags.ForwardDefintion tag followed by a forward forwardReference
/// index which is being defined.
/// * Serializer.Tags.BeginObject tag
/// * A reference to the SerializationType for the object. This reference CANNOT be a
/// forward forwardReference because its value is needed during the deserialization process before
/// forward references are resolved.
/// * All the data that that objects 'IFastSerializable.ToStream method wrote. This is the
/// heart of the deserialized data, and the object itself has a lot of control over this
/// format.
/// * Serializer.Tags.EndObject tag. This marks the end of the object. It quickly finds bugs
/// in ToStream FromStream mismatches, and also allows for V1 deserializers to skip past
/// additional fields added since V1.
///
/// * Serializing Object references:
/// When an object forwardReference is serialized, any of the following may follow in the stream
///
/// * Serializer.Tags.NullReference used to encode a null object forwardReference.
/// * Serializer.Tags.BeginObject or Serializer.Tags.ForwardDefintion, which indicates
/// that this the first time the target object has been referenced, and the target is being
/// serialized on the spot.
/// * Serializer.Tags.ObjectReference which indicates that the target object has already
/// been serialized and what follows is the StreamLabel of where the definition is.
/// * Serializer.Tags.ForwardReference followed by a new forward forwardReference index. This
/// indicates that the object is not yet serialized, but the serializer has chosen not to
/// immediately serialize the object. Ultimately this object will be defined, but has not
/// happened yet.
///
/// * Serializing Types:
/// Types are simply objects of type SerializationType which contain enough information about
/// the type for the Deserializer to do its work (it full name and version number). They are
/// serialized just like all other types. The only thing special about it is that references to
/// types after the BeginObject tag must not be forward references.
///
/// #StreamLayout:
/// The structure of the file as a whole is simply a list of objects. The first and last objects in
/// the file are part of the serialization infrastructure.
///
/// Layout Synopsis
/// * Signature representing Serializer format
/// * EntryObject (most of the rest of the file)
/// * BeginObject tag
/// * Type for This object (which is a object of type SerializationType)
/// * BeginObject tag
/// * Type for SerializationType POSITION1
/// * BeginObject tag
/// * Type for SerializationType
/// * ObjectReference tag // This is how our recursion ends.
/// * StreamLabel for POSITION1
/// * Version Field for SerializationType
/// * Minimum Version Field for SerializationType
/// * FullName string for SerializationType
/// * EndObject tag
/// * Version field for EntryObject's type
/// * Minimum Version field for EntryObject's type
/// * FullName string for EntryObject's type
/// * EndObject tag
/// * Field1
/// * Field2
/// * V2_Field (this should be tagged so that it can be skipped by V1 deserializers.
/// * EndObject tag
/// * ForwardReferenceTable pseudo-object
/// * Count of forward references
/// * StreamLabel for forward ref 0
/// * StreamLabel for forward ref 1.
/// * ...
/// * SerializationTrailer pseudo-object
/// * StreamLabel ForwardReferenceTable
/// * StreamLabel to SerializationTrailer
/// * End of stream
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
sealed class Serializer : IDisposable
{
/// <summary>
/// Create a serializer that writes 'entryObject' to a file.
/// </summary>
/// <param name="filePath">The destination file.</param>
/// <param name="entryObject">The object to serialize.</param>
/// <param name="share">Optional sharing mode for the destination file. Defaults to <see cref="FileShare.Read"/>.</param>
public Serializer(string filePath, IFastSerializable entryObject, FileShare share = FileShare.Read) : this(new IOStreamStreamWriter(filePath, settings: SerializationSettings.Default, share: share), entryObject) { }
/// <summary>
/// Create a serializer that writes <paramref name="entryObject"/> to a <see cref="Stream"/>. The serializer
/// will close the stream when it closes.
/// </summary>
public Serializer(Stream outputStream, IFastSerializable entryObject)
: this(outputStream, entryObject, false)
{
}
/// <summary>
/// Create a serializer that writes <paramref name="entryObject"/> to a <see cref="Stream"/>. The
/// <paramref name="leaveOpen"/> parameter determines whether the serializer will close the stream when it
/// closes.
/// </summary>
public Serializer(Stream outputStream, IFastSerializable entryObject, bool leaveOpen)
: this(new IOStreamStreamWriter(outputStream, SerializationSettings.Default, leaveOpen: leaveOpen), entryObject)
{
}
/// <summary>
/// Create a serializer that writes 'entryObject' another IStreamWriter
/// </summary>
public Serializer(IStreamWriter writer, IFastSerializable entryObject)
{
bool succeeded = false;
try
{
TypesInGraph = new Dictionary<RuntimeTypeHandle, SerializationType>();
ObjectsInGraph = new Dictionary<IFastSerializable, StreamLabel>();
ObjectsWithForwardReferences = new Dictionary<IFastSerializable, ForwardReference>();
this.writer = writer;
Log("<Serializer>");
// Write the header.
Write("!FastSerialization.1");
// Write the main object. This is recursive and does most of the work.
Write(entryObject);
// Write any forward references.
WriteDeferedObjects();
// Write an unbalanced EndObject tag to represent the end of objects.
WriteTag(Tags.EndObject);
// Write the forward forwardReference table (for random access lookup)
StreamLabel forwardRefsLabel = writer.GetLabel();
Log("<ForwardRefTable StreamLabel=\"0x" + forwardRefsLabel.ToString("x") + "\">");
if (forwardReferenceDefinitions != null)
{
Write(forwardReferenceDefinitions.Count);
for (int i = 0; i < forwardReferenceDefinitions.Count; i++)
{
Debug.Assert(forwardReferenceDefinitions[i] != StreamLabel.Invalid);
Log("<ForwardDefEntry index=\"" + i + "\" StreamLabelRef=\"0x" + forwardReferenceDefinitions[i].ToString("x") + "\"/>");
writer.Write(forwardReferenceDefinitions[i]);
}
}
else
{
Write(0);
}
Log("</ForwardRefTable>");
// Write the trailer currently it has only one item in it, however it is expandable.
// items.
StreamLabel trailerLabel = writer.GetLabel();
Log("<Trailer StreamLabel=\"0x" + trailerLabel.ToString("x") + "\">");
Write(forwardRefsLabel);
// More stuff goes here in future versions.
Log("</Trailer>");
Log("<WriteSuffixLabel StreamLabelRef=\"0x" + trailerLabel.ToString("x") + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.WriteSuffixLabel(trailerLabel);
Log("</Serializer>");
succeeded = true;
}
finally
{
if (!succeeded)
{
writer.Dispose();
}
}
}
// Convenience functions.
/// <summary>
/// Write a bool to a stream
/// </summary>
public void Write(bool value)
{
Write((byte)(value ? 1 : 0));
}
/// <summary>
/// Write a byte to a stream
/// </summary>
public void Write(byte value)
{
Log("<Write Type=\"byte\" Value=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write(value);
}
/// <summary>
/// Write a short to a stream
/// </summary>
public void Write(short value)
{
Log("<Write Type=\"short\" Value=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write(value);
}
/// <summary>
/// Write an int to a stream
/// </summary>
public void Write(int value)
{
Log("<Write Type=\"int\" Value=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write(value);
}
/// <summary>
/// Write a long to a stream
/// </summary>
public void Write(long value)
{
Log("<Write Type=\"long\" Value=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write(value);
}
/// <summary>
/// Write a Guid to a stream
/// </summary>
public void Write(Guid value)
{
Log("<Write Type=\"Guid\" Value=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
byte[] bytes = value.ToByteArray();
for (int i = 0; i < bytes.Length; i++)
{
writer.Write(bytes[i]);
}
}
/// <summary>
/// Write a string to a stream
/// </summary>
public void Write(string value)
{
#if DEBUG
if (value == null)
Log("<Write Type=\"null string\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
else
Log("<Write Type=\"string\" Value=" + value + " StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
#endif
writer.Write(value);
}
/// <summary>
/// Write a float to a stream
/// </summary>
public unsafe void Write(float value)
{
int* intPtr = (int*)&value;
writer.Write(*intPtr);
}
/// <summary>
/// Write a double to a stream
/// </summary>
public unsafe void Write(double value)
{
long* longPtr = (long*)&value;
writer.Write(*longPtr);
}
/// <summary>
/// Write a StreamLabel (pointer to some other part of the stream whose location is current known) to the stream
/// </summary>
public void Write(StreamLabel value)
{
Log("<Write Type=\"StreamLabel\" StreamLabelRef=\"0x" + value.ToString("x") + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write(value);
}
/// <summary>
/// Write a ForwardReference (pointer to some other part of the stream that whose location is not currently known) to the stream
/// </summary>
public void Write(ForwardReference value)
{
Log("<Write Type=\"ForwardReference\" indexRef=\"" + value + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write((int)value);
}
/// <summary>
/// If the object is potentially aliased (multiple references to it), you should write it with this method.
/// </summary>
public void Write(IFastSerializable obj) { WriteObjectRef(obj, false); }
/// <summary>
/// To tune working set (or disk seeks), or to make the dump of the format more readable, it is
/// valuable to have control over which of several references to an object will actually cause it to
/// be serialized (by default the first encountered does it).
///
/// WriteDefered allows you to write just a forwardReference to an object with the expectation that
/// somewhere later in the serialization process the object will be serialized. If no call to
/// WriteObject() occurs, then the object is serialized automatically before the stream is closed
/// (thus dangling references are impossible).
/// </summary>
public void WriteDefered(IFastSerializable obj) { WriteObjectRef(obj, true); }
/// <summary>
/// This is an optimized version of WriteObjectReference that can be used in some cases.
///
/// If the object is not aliased (it has an 'owner' and only that owner has references to it (which
/// implies its lifetime is strictly less than its owners), then the serialization system does not
/// need to put the object in the 'interning' table. This saves a space (entries in the intern table
/// as well as 'SyncEntry' overhead of creating hash codes for object) as well as time (to create
/// that bookkeeping) for each object that is treated as private (which can add up if because it is
/// common that many objects are private). The private instances are also marked in the serialized
/// format so on reading there is a similar bookkeeping savings.
///
/// The ultimate bits written by WritePrivateObject are the same as WriteObject.
///
/// TODO Need a DEBUG mode where we detect if others besides the owner reference the object.
/// </summary>
public void WritePrivate(IFastSerializable obj)
{
Log("<WritePrivateObject obj=\"0x" + obj.GetHashCode().ToString("x") +
"\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\">");
WriteObjectData(obj, Tags.BeginPrivateObject);
Log("</WritePrivateObject>");
}
// forward reference support
/// <summary>
/// Create a ForwardReference. At some point before the end of the serialization, DefineForwardReference must be called on this value
/// </summary>
/// <returns></returns>
public ForwardReference GetForwardReference()
{
if (forwardReferenceDefinitions == null)
{
forwardReferenceDefinitions = new List<StreamLabel>();
}
ForwardReference ret = (ForwardReference)forwardReferenceDefinitions.Count;
forwardReferenceDefinitions.Add(StreamLabel.Invalid);
return ret;
}
/// <summary>
/// Define the ForwardReference forwardReference to point at the current write location.
/// </summary>
/// <param name="forwardReference"></param>
public void DefineForwardReference(ForwardReference forwardReference)
{
forwardReferenceDefinitions[(int)forwardReference] = writer.GetLabel();
}
// data added after V1 needs to be tagged so that V1 deserializers can skip it.
/// <summary>
/// Write a byte preceded by a tag that indicates its a byte. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(bool value) { WriteTag(Tags.Byte); Write(value ? (byte)1 : (byte)0); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a byte. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(byte value) { WriteTag(Tags.Byte); Write(value); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a short. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(short value) { WriteTag(Tags.Int16); Write(value); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a int. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(int value) { WriteTag(Tags.Int32); Write(value); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a long. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(long value) { WriteTag(Tags.Int64); Write(value); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a string. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(string value) { WriteTag(Tags.String); Write(value); }
/// <summary>
/// Write a byte preceded by a tag that indicates its a object. These should be read with the corresponding TryReadTagged operation
/// </summary>
public void WriteTagged(IFastSerializable value)
{
WriteTag(Tags.SkipRegion);
ForwardReference endRegion = GetForwardReference();
Write(endRegion); // Allow the reader to skip this.
Write(value); // Write the data we can skip
DefineForwardReference(endRegion); // This is where the forward reference refers to
}
/// <summary>
/// Writes the header for a skipping an arbitrary blob. THus it writes a Blob
/// tag and the size, and the caller must then write 'sizes' bytes of data in
/// some way. This allows you to create regions of arbitrary size that can
/// be skipped by old as well as new parsers.
/// </summary>
/// <param name="size"></param>
public void WriteTaggedBlobHeader(int size)
{
WriteTag(Tags.Blob);
Write(size);
}
/// <summary>
/// Writes an end tag (which is different from all others). This is useful
/// when you have a deferred region of tagged items.
/// </summary>
public void WriteTaggedEnd() { WriteTag(Tags.EndObject); }
/// <summary>
/// Retrieve the underlying stream we are writing to. Generally the Write* methods are enough.
/// </summary>
public IStreamWriter Writer { get { return writer; } }
/// <summary>
/// Completes the writing of the stream.
/// </summary>
public void Close()
{
Dispose();
}
#region private
private StreamWriter log;
/// <summary>
/// To help debug any serialization issues, you can write data to a side file called 'log.serialize.xml'
/// which can track exactly what serialization operations occurred.
/// </summary>
[Conditional("DEBUG_SERIALIZE")]
public void Log(string str)
{
if (log == null)
{
log = File.CreateText("log.serialize.xml");
}
log.WriteLine(str);
}
private void WriteTag(Tags tag)
{
Log("<WriteTag Type=\"" + tag + "\" Value=\"" + ((int)tag).ToString() + "\" StreamLabel=\"0x" + writer.GetLabel().ToString("x") + "\"/>");
writer.Write((byte)tag);
}
private void WriteObjectRef(IFastSerializable obj, bool defered)
{
if (obj == null)
{
Log("<WriteNullReference>");
WriteTag(Tags.NullReference);
Log("</WriteNullReference>");
return;
}
StreamLabel reference;
if (ObjectsInGraph.TryGetValue(obj, out reference))
{
Log("<WriteReference streamLabelRef=\"0x" + reference.ToString("x") +
"\" objRef=\"0x" + obj.GetHashCode().ToString("x") + "\">");
WriteTag(Tags.ObjectReference);
Write(reference);
Log("</WriteReference>");
return;
}
// If we have a forward forwardReference to this, get it.
ForwardReference forwardReference;
if (defered)
{
if (ObjectsWithForwardReferences == null)
{
ObjectsWithForwardReferences = new Dictionary<IFastSerializable, ForwardReference>();
}
if (!ObjectsWithForwardReferences.TryGetValue(obj, out forwardReference))
{
forwardReference = GetForwardReference();
ObjectsWithForwardReferences.Add(obj, forwardReference);
}
Log("<WriteForwardReference indexRef=\"0x" + ((int)forwardReference).ToString("x") +
"\" objRef=\"0x" + obj.GetHashCode().ToString("x") +
"\" type=\"" + obj.GetType().Name + "\">");
WriteTag(Tags.ForwardReference);
// Write the forward forwardReference index
Write((int)forwardReference);
// And its type.
WriteTypeForObject(obj);
Log("</WriteForwardReference>");
return;
}
// At this point we are writing an actual object and not a reference.
//
StreamLabel objLabel = writer.GetLabel();
Log("<WriteObject obj=\"0x" + obj.GetHashCode().ToString("x") +
"\" StreamLabel=\"0x" + objLabel.ToString("x") +
"\" type=\"" + obj.GetType().Name + "\">");
// Have we just defined an object that has a forward forwardReference to it?
if (ObjectsWithForwardReferences != null &&
ObjectsWithForwardReferences.TryGetValue(obj, out forwardReference))
{
Log("<WriteForwardReferenceDefinition index=\"0x" + ((int)forwardReference).ToString("x") + "\">");
// OK, tag the definition with the forward forwardReference index
WriteTag(Tags.ForwardDefinition);
Write((int)forwardReference);
// And also put it in the ForwardReferenceTable.
forwardReferenceDefinitions[(int)forwardReference] = objLabel;
// And we can remove it from the ObjectsWithForwardReferences table
ObjectsWithForwardReferences.Remove(obj);
Log("</WriteForwardReferenceDefinition>");
}
// Add to object graph before calling ToStream (for recursive objects)
ObjectsInGraph.Add(obj, objLabel);
WriteObjectData(obj, Tags.BeginObject);
Log("</WriteObject>");
}
private void WriteTypeForObject(IFastSerializable obj)
{
// Write the type of the forward forwardReference.
RuntimeTypeHandle handle = obj.GetType().TypeHandle;
SerializationType type;
if (!TypesInGraph.TryGetValue(handle, out type))
{
type = CreateTypeForObject(obj);
TypesInGraph.Add(handle, type);
}
Log("<WriteTypeForObject TypeName=\"" + type + "\">");
WriteObjectRef(type, false);
Log("</WriteTypeForObject>");
}
private void WriteObjectData(IFastSerializable obj, Tags beginTag)
{
Debug.Assert(beginTag == Tags.BeginObject || beginTag == Tags.BeginPrivateObject);
WriteTag(beginTag);
WriteTypeForObject(obj);
obj.ToStream(this);
WriteTag(Tags.EndObject);
}
private void WriteDeferedObjects()
{
if (ObjectsWithForwardReferences == null)