Skip to content

Commit a358962

Browse files
authored
Breaking Changes for FastSerialization (#2121)
* - Rename SerializationConfiguration --> SerializationSettings. - Require an instance of SerializationSettings for serializers and deserializers. * Require a SerializationSettings property for both IStreamReader and IStreamWriter to allow writers to directly create readers. * Refactor SerializationSettings to clone on mutation. This ensures that usres can't change the set of allowed types after it is set and handed to the deserialization code. * Refactor FastSerialization factories and add known types. Remove the ability for FastSerialization to create arbitrary types that are specified in serialized files. Instead, force users to register all types before or during serialization. Types can be registered via calls to Deserializer.RegisterType and Deserializer.RegisterFactory. Users can also implement Deserializer.OnUnregisteredType to handle unregistered types encountered during deserialization. It is incumbent on the implementor to not just blindly call Type.GetType in OnUnregisteredType, and instead only create known types. * Add tests and remove RegisterType(string) because users will need to provide enough context to find the right assembly. * Move StreamReaderAlignment into SerializationSettings. * Undo RID change to HeapDump.csproj. * Rename SetXXX --> WithXXX since it doesn't change the current instance. * Simplify based on feedback.
1 parent d1ad99b commit a358962

13 files changed

Lines changed: 235 additions & 139 deletions

File tree

src/FastSerialization/FastSerialization.cs

Lines changed: 101 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,41 @@ enum StreamReaderAlignment : int
9898
};
9999

100100
/// <summary>
101-
/// These settings apply to use of Serializer and Deserializer specifically.
101+
/// Settings used by <code>Serializer</code> and <code>Deserializer</code>.
102102
/// </summary>
103103
#if FASTSERIALIZATION_PUBLIC
104104
public
105105
#endif
106-
sealed class SerializationConfiguration
106+
sealed class SerializationSettings
107107
{
108-
public StreamLabelWidth StreamLabelWidth { get; set; } = StreamLabelWidth.EightBytes;
108+
internal StreamLabelWidth StreamLabelWidth { get; }
109+
110+
internal StreamReaderAlignment StreamReaderAlignment { get; }
111+
112+
public static SerializationSettings Default { get; } = new SerializationSettings(
113+
StreamLabelWidth.EightBytes,
114+
StreamReaderAlignment.EightBytes);
115+
116+
public SerializationSettings WithStreamLabelWidth(StreamLabelWidth width)
117+
{
118+
return new SerializationSettings(
119+
width,
120+
StreamReaderAlignment);
121+
}
122+
123+
public SerializationSettings WithStreamReaderAlignment(StreamReaderAlignment alignment)
124+
{
125+
return new SerializationSettings(
126+
StreamLabelWidth,
127+
alignment);
128+
}
129+
130+
private SerializationSettings(
131+
StreamLabelWidth streamLabelWidth, StreamReaderAlignment streamReaderAlignment)
132+
{
133+
StreamLabelWidth = streamLabelWidth;
134+
StreamReaderAlignment = streamReaderAlignment;
135+
}
109136
}
110137

111138
/// <summary>
@@ -184,6 +211,11 @@ interface IStreamWriter : IDisposable
184211
/// IStreamReader.GotoSuffixLabel for more)
185212
/// </summary>
186213
void WriteSuffixLabel(StreamLabel value);
214+
215+
/// <summary>
216+
/// The settings associated with this writer.
217+
/// </summary>
218+
SerializationSettings Settings { get; }
187219
}
188220

189221

@@ -253,6 +285,11 @@ interface IStreamReader : IDisposable
253285
/// and then seeking to that position.
254286
/// </summary>
255287
void GotoSuffixLabel();
288+
289+
/// <summary>
290+
/// The settings associated with this reader.
291+
/// </summary>
292+
SerializationSettings Settings { get; }
256293
}
257294

258295
#if !DOTNET_V35
@@ -516,7 +553,7 @@ sealed class Serializer : IDisposable
516553
/// <param name="filePath">The destination file.</param>
517554
/// <param name="entryObject">The object to serialize.</param>
518555
/// <param name="share">Optional sharing mode for the destination file. Defaults to <see cref="FileShare.Read"/>.</param>
519-
public Serializer(string filePath, IFastSerializable entryObject, FileShare share = FileShare.Read) : this(new IOStreamStreamWriter(filePath, share: share), entryObject) { }
556+
public Serializer(string filePath, IFastSerializable entryObject, FileShare share = FileShare.Read) : this(new IOStreamStreamWriter(filePath, settings: SerializationSettings.Default, share: share), entryObject) { }
520557

521558
/// <summary>
522559
/// Create a serializer that writes <paramref name="entryObject"/> to a <see cref="Stream"/>. The serializer
@@ -533,7 +570,7 @@ public Serializer(Stream outputStream, IFastSerializable entryObject)
533570
/// closes.
534571
/// </summary>
535572
public Serializer(Stream outputStream, IFastSerializable entryObject, bool leaveOpen)
536-
: this(new IOStreamStreamWriter(outputStream, leaveOpen: leaveOpen), entryObject)
573+
: this(new IOStreamStreamWriter(outputStream, SerializationSettings.Default, leaveOpen: leaveOpen), entryObject)
537574
{
538575
}
539576

@@ -1035,18 +1072,18 @@ sealed class Deserializer : IDisposable
10351072
/// <summary>
10361073
/// Create a Deserializer that reads its data from a given file
10371074
/// </summary>
1038-
public Deserializer(string filePath, SerializationConfiguration config = null)
1075+
public Deserializer(string filePath, SerializationSettings settings)
10391076
{
1040-
IOStreamStreamReader reader = new IOStreamStreamReader(filePath, config);
1077+
IOStreamStreamReader reader = new IOStreamStreamReader(filePath, settings);
10411078
Initialize(reader, filePath);
10421079
}
10431080

10441081
/// <summary>
10451082
/// Create a Deserializer that reads its data from a given System.IO.Stream. The stream will be closed when the Deserializer is done with it.
10461083
/// </summary>
1047-
public Deserializer(Stream inputStream, string streamName, SerializationConfiguration config = null)
1084+
public Deserializer(Stream inputStream, string streamName, SerializationSettings settings)
10481085
{
1049-
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, config: config);
1086+
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, settings: settings);
10501087
Initialize(reader, streamName);
10511088
}
10521089

@@ -1055,9 +1092,9 @@ public Deserializer(Stream inputStream, string streamName, SerializationConfigur
10551092
/// <paramref name="leaveOpen"/> parameter determines whether the deserializer will close the stream when it
10561093
/// closes.
10571094
/// </summary>
1058-
public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationConfiguration config = null)
1095+
public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationSettings settings)
10591096
{
1060-
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, config: config);
1097+
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, settings: settings);
10611098
Initialize(reader, streamName);
10621099
}
10631100

@@ -1610,38 +1647,61 @@ public StreamLabel ResolveForwardReference(ForwardReference reference, bool pres
16101647
public String Name { get; private set; }
16111648

16121649
/// <summary>
1613-
/// If set this function is set, then it is called whenever a type name from the serialization
1614-
/// data is encountered. It is your you then need to look that up. If it is not present
1615-
/// it uses Type.GetType(string) which only checks the current assembly and mscorlib.
1650+
/// Called when <code>Deserializer</code> encounters a type that is not registered, allowing the implementation
1651+
/// to return a factory delegate to be cached and used for subsequent encounters.
16161652
/// </summary>
1617-
public Func<string, Type> TypeResolver { get; set; }
1653+
public Func<string, Func<IFastSerializable>> OnUnregisteredType { get; set; }
16181654

16191655
/// <summary>
1620-
/// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects
1621-
/// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory'
1622-
/// delegate for that type. Thus all types being deserialized must have a factory.
1656+
/// Registers a creation factory for a type.
16231657
///
1624-
/// RegisterFactory registers such a factory for particular 'type'.
1658+
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
1659+
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
1660+
/// must either be registered by calling RegisterFactory or RegisterType.
16251661
/// </summary>
16261662
public void RegisterFactory(Type type, Func<IFastSerializable> factory)
16271663
{
16281664
factories[type.FullName] = factory;
16291665
}
1666+
1667+
/// <summary>
1668+
/// Registers a creation factory for a type name.
1669+
///
1670+
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
1671+
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
1672+
/// must either be registered by calling RegisterFactory or RegisterType.
1673+
/// </summary>
16301674
public void RegisterFactory(string typeName, Func<IFastSerializable> factory)
16311675
{
16321676
factories[typeName] = factory;
16331677
}
16341678

16351679
/// <summary>
1636-
/// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects
1637-
/// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory'
1638-
/// delegate for that type. Thus all types being deserialized must have a factory.
1680+
/// Registers a type that can be created by instantiating the parameterless constructor.
16391681
///
1640-
/// RegisterDefaultFactory registers a factory that is passed a type parameter and returns a new IFastSerialable object.
1682+
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
1683+
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
1684+
/// must either be registered by calling RegisterFactory or RegisterType.
16411685
/// </summary>
1642-
public void RegisterDefaultFactory(Func<Type, IFastSerializable> defaultFactory)
1686+
public void RegisterType(Type type)
1687+
{
1688+
RegisterFactory(type, () =>
1689+
{
1690+
return CreateDefault(type);
1691+
});
1692+
}
1693+
1694+
private static IFastSerializable CreateDefault(Type type)
16431695
{
1644-
this.defaultFactory = defaultFactory;
1696+
try
1697+
{
1698+
return (IFastSerializable)Activator.CreateInstance(type);
1699+
}
1700+
catch (MissingMethodException)
1701+
{
1702+
throw new SerializationException(
1703+
$"Unable to create an object of type {type.FullName}. It must either have a parameterless constructor or have been registered with the deserializer via RegisterFactory.");
1704+
}
16451705
}
16461706

16471707
// For FromStream method bodies, reading tagged values (for post V1 field additions)
@@ -1964,49 +2024,31 @@ private IFastSerializable ReadObjectDefinition(Tags tag, StreamLabel objectLabel
19642024

19652025
internal Func<IFastSerializable> GetFactory(string fullName)
19662026
{
1967-
Func<IFastSerializable> ret;
1968-
if (factories.TryGetValue(fullName, out ret))
2027+
// Check for a registered factory.
2028+
Func<IFastSerializable> factory = null;
2029+
if (factories.TryGetValue(fullName, out factory))
19692030
{
1970-
return ret;
2031+
return factory;
19712032
}
19722033

1973-
Type type;
1974-
if (TypeResolver != null)
1975-
{
1976-
type = TypeResolver(fullName);
1977-
}
1978-
else
2034+
// If there is not a registered factory, give the implementation of IFastSerializable an opportunity to create a factory.
2035+
if (OnUnregisteredType != null)
19792036
{
1980-
type = Type.GetType(fullName);
2037+
factory = OnUnregisteredType(fullName);
2038+
if (factory != null)
2039+
{
2040+
// Save the factory for future encounters of this type name.
2041+
RegisterFactory(fullName, factory);
2042+
}
19812043
}
19822044

1983-
if (type == null)
2045+
if (factory == null)
19842046
{
1985-
throw new TypeLoadException("Could not find type " + fullName);
2047+
throw new TypeLoadException(
2048+
$"Could not create an instance of type {fullName}. The type must be registered with the deserializer via a call to RegisterFactory or RegisterType.");
19862049
}
19872050

1988-
return delegate
1989-
{
1990-
// If we have a default factory, use it.
1991-
if (defaultFactory != null)
1992-
{
1993-
IFastSerializable instance = defaultFactory(type);
1994-
if (instance != null)
1995-
{
1996-
return instance;
1997-
}
1998-
}
1999-
// Factory of last resort.
2000-
try
2001-
{
2002-
return (IFastSerializable)Activator.CreateInstance(type);
2003-
}
2004-
catch (MissingMethodException)
2005-
{
2006-
throw new SerializationException("Failure deserializing " + type.FullName +
2007-
".\r\nIt must either have a parameterless constructor or been registered with the serializer.");
2008-
}
2009-
};
2051+
return factory;
20102052
}
20112053

20122054
private void FindEndTag(SerializationType type, IFastSerializable objectBeingDeserialized)
@@ -2144,7 +2186,6 @@ private Tags ReadTag()
21442186
/// </summary>
21452187
internal bool deferForwardReferences;
21462188
private Dictionary<string, Func<IFastSerializable>> factories;
2147-
private Func<Type, IFastSerializable> defaultFactory;
21482189
#endregion
21492190
};
21502191

src/FastSerialization/MemoryMappedFileStreamReader.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ public class MemoryMappedFileStreamReader : IStreamReader
2929
private long _capacity;
3030
private long _offset;
3131

32-
public MemoryMappedFileStreamReader(string mapName, long length)
33-
: this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false)
32+
public MemoryMappedFileStreamReader(string mapName, long length, SerializationSettings settings)
33+
: this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false, settings)
3434
{
3535
}
3636

37-
public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen)
37+
public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen, SerializationSettings settings)
3838
{
3939
_file = file;
4040
_fileLength = length;
4141
_leaveOpen = leaveOpen;
4242

43+
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
44+
4345
if (IntPtr.Size == 4)
4446
{
4547
_capacity = Math.Min(_fileLength, BlockCopyCapacity);
@@ -53,11 +55,17 @@ public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool lea
5355
_viewAddress = _view.SafeMemoryMappedViewHandle.DangerousGetHandle();
5456
}
5557

56-
public static MemoryMappedFileStreamReader CreateFromFile(string path)
58+
public static MemoryMappedFileStreamReader CreateFromFile(string path, SerializationSettings settings)
5759
{
5860
long capacity = new FileInfo(path).Length;
5961
MemoryMappedFile file = MemoryMappedFile.CreateFromFile(path, FileMode.Open, Guid.NewGuid().ToString("N"), capacity, MemoryMappedFileAccess.Read);
60-
return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false);
62+
return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false, settings);
63+
}
64+
65+
public SerializationSettings Settings
66+
{
67+
get;
68+
private set;
6169
}
6270

6371
public DeferedStreamLabel Current

src/FastSerialization/SegmentedMemoryStreamReader.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ public class SegmentedMemoryStreamReader
1212
/// <summary>
1313
/// Create a IStreamReader (reads binary data) from a given byte buffer
1414
/// </summary>
15-
public SegmentedMemoryStreamReader(SegmentedList<byte> data, SerializationConfiguration config = null) : this(data, 0, data.Count, config) { }
15+
public SegmentedMemoryStreamReader(SegmentedList<byte> data, SerializationSettings settings) : this(data, 0, data.Count, settings) { }
1616
/// <summary>
1717
/// Create a IStreamReader (reads binary data) from a given subregion of a byte buffer
1818
/// </summary>
19-
public SegmentedMemoryStreamReader(SegmentedList<byte> data, long start, long length, SerializationConfiguration config = null)
19+
public SegmentedMemoryStreamReader(SegmentedList<byte> data, long start, long length, SerializationSettings settings)
2020
{
21-
SerializationConfiguration = config ?? new SerializationConfiguration();
21+
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
2222

23-
if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
23+
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
2424
{
2525
readLabel = () =>
2626
{
@@ -157,7 +157,7 @@ public virtual void Goto(StreamLabel label)
157157
{
158158
Debug.Assert(label != StreamLabel.Invalid);
159159

160-
if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
160+
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
161161
{
162162
Debug.Assert((long)label <= int.MaxValue);
163163
position = (uint)label;
@@ -174,7 +174,7 @@ public virtual StreamLabel Current
174174
{
175175
get
176176
{
177-
if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
177+
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
178178
{
179179
return (StreamLabel)(uint)position;
180180
}
@@ -206,9 +206,9 @@ public void Dispose()
206206
protected virtual void Dispose(bool disposing) { }
207207

208208
/// <summary>
209-
/// Returns the SerializationConfiguration for this stream reader.
209+
/// Returns the <code>SerializationSettings</code> for this stream reader.
210210
/// </summary>
211-
internal SerializationConfiguration SerializationConfiguration { get; private set; }
211+
internal SerializationSettings Settings { get; private set; }
212212
#endregion
213213

214214
#region private

src/FastSerialization/SegmentedMemoryStreamWriter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ namespace FastSerialization
77
{
88
public class SegmentedMemoryStreamWriter
99
{
10-
public SegmentedMemoryStreamWriter(SerializationConfiguration config = null) : this(64, config) { }
11-
public SegmentedMemoryStreamWriter(long initialSize, SerializationConfiguration config = null)
10+
public SegmentedMemoryStreamWriter(SerializationSettings settings) : this(64, settings) { }
11+
public SegmentedMemoryStreamWriter(long initialSize, SerializationSettings settings)
1212
{
13-
SerializationConfiguration = config ?? new SerializationConfiguration();
13+
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
1414

15-
if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
15+
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
1616
{
1717
writeLabel = (value) =>
1818
{
@@ -100,14 +100,14 @@ public void WriteToStream(Stream outputStream)
100100
public SegmentedMemoryStreamReader GetReader()
101101
{
102102
var readerBytes = bytes;
103-
return new SegmentedMemoryStreamReader(readerBytes, 0, readerBytes.Count, SerializationConfiguration);
103+
return new SegmentedMemoryStreamReader(readerBytes, 0, readerBytes.Count, Settings);
104104
}
105105
public void Dispose() { }
106106

107107
/// <summary>
108-
/// Returns the SerializationConfiguration for this stream writer.
108+
/// Returns the SerializerSettings for this stream writer.
109109
/// </summary>
110-
internal SerializationConfiguration SerializationConfiguration { get; private set; }
110+
internal SerializationSettings Settings { get; private set; }
111111

112112
#region private
113113
protected virtual void MakeSpace()

0 commit comments

Comments
 (0)