Open sandboxFocusImprove this doc

LAMA0204: The serializable base type from a referenced project has no usable constructor

Severity Error
Reported by Metalama.Framework
Message Cannot generate a compile-time serializer for '<Type>' because the base type '<BaseType>', declared in a referenced assembly, is serializable but has neither an accessible parameterless constructor nor an accessible deserializing constructor with a single parameter of type IArgumentsReader. Add one of these constructors to '<BaseType>'.

Cause

When Metalama builds the compile-time code of a project, it generates a serializer and a deserializing constructor, which takes a single IArgumentsReader parameter, for every type that implements ICompileTimeSerializable and doesn't have a manual serializer. The generated deserializing constructor must call a constructor of the base type: the base type's deserializing constructor if it has one, otherwise its parameterless constructor.

Metalama reports LAMA0204 when the type <Type> derives from a serializable type <BaseType> declared in a referenced project, and <BaseType> has neither a public or protected deserializing constructor nor a public or protected parameterless constructor. Because Metalama always generates a deserializing constructor for the types whose serializer it generates, this situation typically happens when <BaseType> has a manual serializer, that is, a nested class derived from ReferenceTypeSerializer.

In the following example, the LogCategory class, declared in a referenced project, has a manual serializer and a single constructor, which requires a name. The DatabaseLogCategory class derives from it in the current project.

1using Metalama.Framework.Aspects;
2
3namespace Doc.LAMA0204.Error;
4
5// Error: the base type LogCategory, declared in a referenced project, has no constructor that Metalama can call.
6[RunTimeOrCompileTime]
Error LAMA0204: Cannot generate a compile-time serializer for 'DatabaseLogCategory' because the base type 'LogCategory', declared in a referenced assembly, is serializable but has neither an accessible parameterless constructor nor an accessible deserializing constructor with a single parameter of type IArgumentsReader. Add one of these constructors to 'LogCategory'.

7public class DatabaseLogCategory : LogCategory
8{
9    public int Level { get; }
10
11    public DatabaseLogCategory( int level ) : base( "Database" )
12    {
13        this.Level = level;
14    }
15}
16
using Metalama.Framework.Aspects;
using Metalama.Framework.Serialization;
using System;

namespace Doc.LAMA0204.Error;

[RunTimeOrCompileTime]
public class LogCategory : ICompileTimeSerializable
{
    public string Name { get; }

    // LogCategory has neither a parameterless constructor nor a deserializing constructor.
    public LogCategory( string name )
    {
        this.Name = name;
    }

    // A manual serializer, so Metalama doesn't generate a deserializing constructor.
    public class Serializer : ReferenceTypeSerializer
    {
        public override object CreateInstance( Type type, IArgumentsReader constructorArguments )
            => new LogCategory( constructorArguments.GetValue<string>( "Name" ) ?? "" );

        public override void SerializeObject( object obj, IArgumentsWriter constructorArguments, IArgumentsWriter initializationArguments )
            => constructorArguments.SetValue( "Name", ((LogCategory) obj).Name );

        public override void DeserializeFields( object obj, IArgumentsReader initializationArguments ) { }
    }
}

Resolution

Edit the base type in the referenced project, and do one of the following:

  • Add a public or protected deserializing constructor, with a single parameter of type IArgumentsReader, that reads the values written by the manual serializer. The generated deserializing constructor of the derived type then calls it. Use this option when the base type has read-only state.
  • Add a public or protected parameterless constructor. The generated deserializing constructor of the derived type then calls it, and the manual serializer of the base type must restore the state of the base type in its DeserializeFields method.

In the following example, LogCategory has a deserializing constructor, which its own serializer also uses.

1using Metalama.Framework.Aspects;
2
3namespace Doc.LAMA0204.Fixed;
4
5// Fixed: the base type LogCategory now has a deserializing constructor.
6[RunTimeOrCompileTime]
7public class DatabaseLogCategory : LogCategory
8{
9    public int Level { get; }
10
11    public DatabaseLogCategory( int level ) : base( "Database" )
12    {
13        this.Level = level;
14    }
15}
16
using Metalama.Framework.Aspects;
using Metalama.Framework.Serialization;
using System;

namespace Doc.LAMA0204.Fixed;

[RunTimeOrCompileTime]
public class LogCategory : ICompileTimeSerializable
{
    public string Name { get; }

    public LogCategory( string name )
    {
        this.Name = name;
    }

    // Fixed: a deserializing constructor that derived types in other projects can call.
    protected LogCategory( IArgumentsReader reader )
    {
        this.Name = reader.GetValue<string>( "Name" ) ?? "";
    }

    // A manual serializer, so Metalama doesn't generate a deserializing constructor.
    public class Serializer : ReferenceTypeSerializer
    {
        public override object CreateInstance( Type type, IArgumentsReader constructorArguments )
            => new LogCategory( constructorArguments );

        public override void SerializeObject( object obj, IArgumentsWriter constructorArguments, IArgumentsWriter initializationArguments )
            => constructorArguments.SetValue( "Name", ((LogCategory) obj).Name );

        public override void DeserializeFields( object obj, IArgumentsReader initializationArguments ) { }
    }
}

See also