Open sandboxFocusImprove this doc

Serialization of aspects and other compile-time classes

Metalama relies on serialization to handle situations when an aspect or cross-project effect, i.e., when it affects not only the current project but also, transitively, referencing projects.

This happens in the following scenarios:

When any aspect or fabric has some cross-project effect, the following process is executed:

  • In the current project:
    • The objects are serialized into a binary stream.
    • The binary stream is stored in a managed resource in the current project.
  • In all referenced projects:
    • The objects are deserialized from the managed resource.

How are objects serialized?

Metalama uses a custom serializer, which is implemented in the Metalama.Framework.Serialization namespace and has a similar behavior as Microsoft's legacy BinaryFormatter serializable.

Unlike more familiar JSON or XML serializers, Metalama's serializer:

  • supports cyclic graphs instead of just trees,
  • serializes the inner object structure, i.e., private fields, instead of the public interface.

These characteristics allow the serialization process to happen almost transparently.

System-defined serializable types

The following types are serializable by default:

Warning

Code model declarations (IDeclaration) and types (IType) are, by design, NOT serializable. If you want to serialize a declaration, you must serialize a reference to it, obtained through the ToRef method. The deserialized reference must then be resolved in its new context using the IRef.GetTarget method.

Custom serializable types

Metalama automatically generates serializers for any type deriving from the ICompileTimeSerializable interface. This includes any aspect, fabric, or class implementing IAspectState, IAnnotation, IHierarchicalOptions, BaseReferenceValidator, ReferencePredicate, ...

You normally don't need to worry about the serialization process since it should usually work transparently. However, here are a few tricks to cope with corner cases:

Skipping a field or property

To waive a field or automatic property from being serialized, annotate it with the [NonCompileTimeSerialized] attribute.

Overriding the serializer when you own the type

If you can edit the source code of the class, you can override the default serializer by adding a nested class called Serializer and implementing the ValueTypeSerializer<T> or ReferenceTypeSerializer<T> class. Your nested class must have a default public constructor.

Implementing a serializer for a third-party type

If you must implement serialization for a class whose you don't own the source code (or to which you don't want to add a package reference to Metalama), follow these steps:

  1. Create a class derived from ValueTypeSerializer<T> or ReferenceTypeSerializer<T> class. The class must have a default public constructor.
  2. Register the serializer by using the assembly-level ImportSerializerAttribute.

For generic types, the serializer type must have the same type arguments as the serialized type.

Security and obfuscation

Although it is inspired by Microsoft's BinaryFormatter, which has been deprecated for security reasons, using the Metalama.Framework.Serialization namespace does not pause a security risk. Although the serializer might in theory allow for arbitrary code execution, it is only designed to deserialize binary data stored in a binary library. Since this library also, in essence, allows for arbitrary code execution, the use of the serializer does not increase the risk. Developers should not use untrusted libraries in the first place.

Warning

The Metalama.Framework.Serialization namespace is NOT compatible with obfuscation. The serialized binary stream contains full names of declarations in clear text, partially defeating the purpose of serialization. Additionally, serialization will fail if these names are changed after compilation by the obfuscation process.