Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking Changes for FastSerialization #2121

Merged
161 changes: 101 additions & 60 deletions src/FastSerialization/FastSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,41 @@ enum StreamReaderAlignment : int
};

/// <summary>
/// These settings apply to use of Serializer and Deserializer specifically.
/// Settings used by <code>Serializer</code> and <code>Deserializer</code>.
/// </summary>
#if FASTSERIALIZATION_PUBLIC
public
#endif
sealed class SerializationConfiguration
sealed class SerializationSettings
{
public StreamLabelWidth StreamLabelWidth { get; set; } = StreamLabelWidth.EightBytes;
internal StreamLabelWidth StreamLabelWidth { get; }

internal StreamReaderAlignment StreamReaderAlignment { get; }

public static SerializationSettings Default { get; } = new SerializationSettings(
StreamLabelWidth.EightBytes,
StreamReaderAlignment.EightBytes);

public SerializationSettings SetStreamLabelWidth(StreamLabelWidth width)
{
return new SerializationSettings(
width,
StreamReaderAlignment);
}

public SerializationSettings SetStreamReaderAlignment(StreamReaderAlignment alignment)
{
return new SerializationSettings(
StreamLabelWidth,
alignment);
}

private SerializationSettings(
StreamLabelWidth streamLabelWidth, StreamReaderAlignment streamReaderAlignment)
{
StreamLabelWidth = streamLabelWidth;
StreamReaderAlignment = streamReaderAlignment;
}
}

/// <summary>
Expand Down Expand Up @@ -184,6 +211,11 @@ interface IStreamWriter : IDisposable
/// IStreamReader.GotoSuffixLabel for more)
/// </summary>
void WriteSuffixLabel(StreamLabel value);

/// <summary>
/// The settings associated with this writer.
/// </summary>
SerializationSettings Settings { get; }
}


Expand Down Expand Up @@ -253,6 +285,11 @@ interface IStreamReader : IDisposable
/// and then seeking to that position.
/// </summary>
void GotoSuffixLabel();

/// <summary>
/// The settings associated with this reader.
/// </summary>
SerializationSettings Settings { get; }
}

#if !DOTNET_V35
Expand Down Expand Up @@ -516,7 +553,7 @@ sealed class Serializer : IDisposable
/// <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, share: share), entryObject) { }
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
Expand All @@ -533,7 +570,7 @@ public Serializer(Stream outputStream, IFastSerializable entryObject)
/// closes.
/// </summary>
public Serializer(Stream outputStream, IFastSerializable entryObject, bool leaveOpen)
: this(new IOStreamStreamWriter(outputStream, leaveOpen: leaveOpen), entryObject)
: this(new IOStreamStreamWriter(outputStream, SerializationSettings.Default, leaveOpen: leaveOpen), entryObject)
{
}

Expand Down Expand Up @@ -1035,18 +1072,18 @@ sealed class Deserializer : IDisposable
/// <summary>
/// Create a Deserializer that reads its data from a given file
/// </summary>
public Deserializer(string filePath, SerializationConfiguration config = null)
public Deserializer(string filePath, SerializationSettings settings)
{
IOStreamStreamReader reader = new IOStreamStreamReader(filePath, config);
IOStreamStreamReader reader = new IOStreamStreamReader(filePath, settings);
Initialize(reader, filePath);
}

/// <summary>
/// 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.
/// </summary>
public Deserializer(Stream inputStream, string streamName, SerializationConfiguration config = null)
public Deserializer(Stream inputStream, string streamName, SerializationSettings settings)
{
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, config: config);
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, settings: settings);
Initialize(reader, streamName);
}

Expand All @@ -1055,9 +1092,9 @@ public Deserializer(Stream inputStream, string streamName, SerializationConfigur
/// <paramref name="leaveOpen"/> parameter determines whether the deserializer will close the stream when it
/// closes.
/// </summary>
public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationConfiguration config = null)
public Deserializer(Stream inputStream, string streamName, bool leaveOpen, SerializationSettings settings)
{
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, config: config);
IOStreamStreamReader reader = new IOStreamStreamReader(inputStream, leaveOpen: leaveOpen, settings: settings);
Initialize(reader, streamName);
}

Expand Down Expand Up @@ -1625,38 +1662,61 @@ public StreamLabel ResolveForwardReference(ForwardReference reference, bool pres
public String Name { get; private set; }

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

/// <summary>
/// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects
/// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory'
/// delegate for that type. Thus all types being deserialized must have a factory.
/// Registers a creation factory for a type.
///
/// RegisterFactory registers such a factory for particular 'type'.
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
/// must either be registered by calling RegisterFactory or RegisterType.
/// </summary>
public void RegisterFactory(Type type, Func<IFastSerializable> factory)
{
factories[type.FullName] = factory;
}

/// <summary>
/// Registers a creation factory for a type name.
///
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
/// must either be registered by calling RegisterFactory or RegisterType.
/// </summary>
public void RegisterFactory(string typeName, Func<IFastSerializable> factory)
{
factories[typeName] = factory;
}

/// <summary>
/// For every IFastSerializable object being deserialized, the Deserializer needs to create 'empty' objects
/// that 'FromStream' is invoked on. The Deserializer gets these 'empty' objects by calling a 'factory'
/// delegate for that type. Thus all types being deserialized must have a factory.
/// Registers a type that can be created by instantiating the parameterless constructor.
///
/// RegisterDefaultFactory registers a factory that is passed a type parameter and returns a new IFastSerialable object.
/// When the <code>Deserializer</code>encounters a serialized type, it will look for a registered factory or type registration
/// so that it knows how to construct an empty instance of the type that can be filled. All non-primitive types
/// must either be registered by calling RegisterFactory or RegisterType.
/// </summary>
public void RegisterDefaultFactory(Func<Type, IFastSerializable> defaultFactory)
public void RegisterType(Type type)
{
RegisterFactory(type, () =>
{
return CreateDefault(type);
});
}

private static IFastSerializable CreateDefault(Type type)
{
this.defaultFactory = defaultFactory;
try
{
return (IFastSerializable)Activator.CreateInstance(type);
}
catch (MissingMethodException)
{
throw new SerializationException(
$"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.");
}
}

// For FromStream method bodies, reading tagged values (for post V1 field additions)
Expand Down Expand Up @@ -1979,49 +2039,31 @@ private IFastSerializable ReadObjectDefinition(Tags tag, StreamLabel objectLabel

internal Func<IFastSerializable> GetFactory(string fullName)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GrabYourPitchforks, you will be most interested in the changes to GetFactory.

{
Func<IFastSerializable> ret;
if (factories.TryGetValue(fullName, out ret))
// Check for a registered factory.
Func<IFastSerializable> factory = null;
if (factories.TryGetValue(fullName, out factory))
{
return ret;
return factory;
}

Type type;
if (TypeResolver != null)
{
type = TypeResolver(fullName);
}
else
// If there is not a registered factory, give the implementation of IFastSerializable an opportunity to create a factory.
if (OnUnregisteredType != null)
{
type = Type.GetType(fullName);
factory = OnUnregisteredType(fullName);
if (factory != null)
{
// Save the factory for future encounters of this type name.
RegisterFactory(fullName, factory);
}
}

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

return delegate
{
// If we have a default factory, use it.
if (defaultFactory != null)
{
IFastSerializable instance = defaultFactory(type);
if (instance != null)
{
return instance;
}
}
// Factory of last resort.
try
{
return (IFastSerializable)Activator.CreateInstance(type);
}
catch (MissingMethodException)
{
throw new SerializationException("Failure deserializing " + type.FullName +
".\r\nIt must either have a parameterless constructor or been registered with the serializer.");
}
};
return factory;
}

private void FindEndTag(SerializationType type, IFastSerializable objectBeingDeserialized)
Expand Down Expand Up @@ -2159,7 +2201,6 @@ private Tags ReadTag()
/// </summary>
internal bool deferForwardReferences;
private Dictionary<string, Func<IFastSerializable>> factories;
private Func<Type, IFastSerializable> defaultFactory;
#endregion
};

Expand Down
22 changes: 17 additions & 5 deletions src/FastSerialization/MemoryMappedFileStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,23 @@ public class MemoryMappedFileStreamReader : IStreamReader
private long _capacity;
private long _offset;

public MemoryMappedFileStreamReader(string mapName, long length)
: this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false)
public MemoryMappedFileStreamReader(string mapName, long length, SerializationSettings settings)
: this(MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read), length, leaveOpen: false, settings)
{
}

public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen)
public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool leaveOpen, SerializationSettings settings)
{
_file = file;
_fileLength = length;
_leaveOpen = leaveOpen;

if (settings == null)
{
throw new ArgumentNullException(nameof(settings));
}
Settings = settings;

if (IntPtr.Size == 4)
{
_capacity = Math.Min(_fileLength, BlockCopyCapacity);
Expand All @@ -53,11 +59,17 @@ public MemoryMappedFileStreamReader(MemoryMappedFile file, long length, bool lea
_viewAddress = _view.SafeMemoryMappedViewHandle.DangerousGetHandle();
}

public static MemoryMappedFileStreamReader CreateFromFile(string path)
public static MemoryMappedFileStreamReader CreateFromFile(string path, SerializationSettings settings)
{
long capacity = new FileInfo(path).Length;
MemoryMappedFile file = MemoryMappedFile.CreateFromFile(path, FileMode.Open, Guid.NewGuid().ToString("N"), capacity, MemoryMappedFileAccess.Read);
return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false);
return new MemoryMappedFileStreamReader(file, capacity, leaveOpen: false, settings);
}

public SerializationSettings Settings
{
get;
private set;
}

public DeferedStreamLabel Current
Expand Down
20 changes: 12 additions & 8 deletions src/FastSerialization/SegmentedMemoryStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ public class SegmentedMemoryStreamReader
/// <summary>
/// Create a IStreamReader (reads binary data) from a given byte buffer
/// </summary>
public SegmentedMemoryStreamReader(SegmentedList<byte> data, SerializationConfiguration config = null) : this(data, 0, data.Count, config) { }
public SegmentedMemoryStreamReader(SegmentedList<byte> data, SerializationSettings settings) : this(data, 0, data.Count, settings) { }
/// <summary>
/// Create a IStreamReader (reads binary data) from a given subregion of a byte buffer
/// </summary>
public SegmentedMemoryStreamReader(SegmentedList<byte> data, long start, long length, SerializationConfiguration config = null)
public SegmentedMemoryStreamReader(SegmentedList<byte> data, long start, long length, SerializationSettings settings)
{
SerializationConfiguration = config ?? new SerializationConfiguration();
if (settings == null)
{
throw new ArgumentNullException(nameof(settings));
}

if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
Settings = settings;
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
{
readLabel = () =>
{
Expand Down Expand Up @@ -157,7 +161,7 @@ public virtual void Goto(StreamLabel label)
{
Debug.Assert(label != StreamLabel.Invalid);

if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
{
Debug.Assert((long)label <= int.MaxValue);
position = (uint)label;
Expand All @@ -174,7 +178,7 @@ public virtual StreamLabel Current
{
get
{
if (SerializationConfiguration.StreamLabelWidth == StreamLabelWidth.FourBytes)
if (Settings.StreamLabelWidth == StreamLabelWidth.FourBytes)
{
return (StreamLabel)(uint)position;
}
Expand Down Expand Up @@ -206,9 +210,9 @@ public void Dispose()
protected virtual void Dispose(bool disposing) { }

/// <summary>
/// Returns the SerializationConfiguration for this stream reader.
/// Returns the <code>SerializationSettings</code> for this stream reader.
/// </summary>
internal SerializationConfiguration SerializationConfiguration { get; private set; }
internal SerializationSettings Settings { get; private set; }
#endregion

#region private
Expand Down
Loading
Loading