Skip to content

[Breaking change]: Legacy serialization infrastructure APIs marked obsolete #34893

Closed
@GrabYourPitchforks

Description

@GrabYourPitchforks

Description

Beginning with .NET 8 Preview 4, most of the legacy serialization infrastructure (the infrastructure which supports SerializableAttribute and ISerializable, along with infrastructure which supports BinaryFormatter) is obsolete.

Additionally, the entirety of the type BinaryFormatter is now obsolete as error.

(See related breaking change notification #34891.)

Version

.NET 8 Preview 4

Previous behavior

In .NET 7, the Serialize and Deserialize methods on BinaryFormatter, IFormatter, and Formatter were marked obsolete. The types themselves, however, were not marked obsolete. The full list of APIs marked obsolete in .NET 7 is provided at the .NET 7 breaking change notification document.

New behavior

Beginning with .NET 8 Preview 4, the entirety of the BinaryFormatter, IFormatter, and Formatter types are marked obsolete as error. Code which references these types - even if it does not call the Serialize or Deserialize method - will observe compilation failures on .NET 8.

Additionally, beginning with .NET 8 Preview 4, many types and APIs which support the legacy serialization infrastructure are marked obsolete as warning. Other APIs are marked [EditorBrowsable(EditorBrowsableState.Never)], which causes these APIs to be hidden from the Visual Studio IDE.

See below for resolution strategies, including getting existing code which relied on BinaryFormatter to compile successfully.

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load or execute, and if so, require recompilation.
  • Source incompatible: When recompiled using the new SDK or component or to target the new runtime, existing source code may require source changes to compile successfully.
  • Behavioral change: Existing binaries may behave differently at run time.

Reason for change

This is the next stage of the BinaryFormatter obsoletion plan, preparing for BinaryFormatter's eventual removal from .NET. See earlier breaking change notifications for additional context:

Recommended action

If you're using BinaryFormatter

The best course of action is to migrate away from it due to its security and reliability flaws. See https://aka.ms/binaryformatter for more information. If necessary, you can suppress the compilation error by following the steps in the Recommended action section of the .NET 7 breaking change notification.

If you're using FormatterServices.GetUninitializedObject

Use RuntimeHelpers.GetUninitializedObject instead.

If you're cross-compiling for .NET Framework and modern .NET, you can use an #if statement to selectively call the appropriate API, as shown below.

Type typeToInstantiate;
#if NET5_0_OR_GREATER
object obj = System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(typeToInstantiate);
#else
object obj = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeToInstantiate);
#endif

If you're creating a custom System.Exception-derived type

Consider whether you truly need your custom exception type to be serializable. Chances are you do not need it to be serializable, as exception serialization is primarily intended to support remoting, and support for remoting was dropped in .NET Core 1.0.

If you have defined your custom exception type like this:

[Serializable]
public class MyException : Exception
{
    public MyException() { /* your ctor logic here */ }
    public MyException(string message) : base(message) { /* your ctor logic here */ }
    public MyException(string message, Exception inner) : base(message, inner) { /* your ctor logic here */ }
    protected MyException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        /* your rehydration logic here */
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        /* your dehydration here */

        base.GetObjectData(info, context);
    }
}

Consider simply removing the [Serializable] attribute, the serialization constructor, and the GetObjectData method override, as shown below.

// [Serializable] <-- Remove this attribute.
public class MyException : Exception
{
    public MyException() { /* your ctor logic here */ }
    public MyException(string message) : base(message) { /* your ctor logic here */ }
    public MyException(string message, Exception inner) : base(message, inner) { /* your ctor logic here */ }

    // Remove the constructor below.
    // protected MyException(SerializationInfo info, StreamingContext context)
    //     : base(info, context)
    // {
    //     /* your rehydration logic here */
    // }

    // Remove the method below.
    // public override void GetObjectData(SerializationInfo info, StreamingContext context)
    // {
    //     /* your dehydration here */
    // 
    //     base.GetObjectData(info, context);
    // }
}

There may be some cases where you cannot remove these APIs from your custom exception types. This might occur if you produce a library constrained by API compatibility requirements. In this case, the recommendation is to obsolete your own serialization constructor and GetObjectData methods using the SYSLIB0051 diagnostic code, as shown below. Since ideally nobody outside the serialization infrastructure itself should be calling these APIs, this should only impact other types which subclass your custom exception type. It should not virally impact anybody catching, constructing, or otherwise using your custom exception type.

[Serializable]
public class MyException : Exception
{
    public MyException() { /* your ctor logic here */ }
    public MyException(string message) : base(message) { /* your ctor logic here */ }
    public MyException(string message, Exception inner) : base(message, inner) { /* your ctor logic here */ }

    [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor
    protected MyException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        /* your rehydration logic here */
    }

    [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to GetObjectData
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        /* your dehydration here */

        base.GetObjectData(info, context);
    }
}

If you're cross-targeting for .NET Framework and .NET 8+, you can use an #if statement to apply the obsoletion conditionally. This is the same strategy we use within the .NET libraries code base when we're cross-targeting runtimes.

[Serializable]
public class MyException : Exception
{
    public MyException() { /* your ctor logic here */ }
    public MyException(string message) : base(message) { /* your ctor logic here */ }
    public MyException(string message, Exception inner) : base(message, inner) { /* your ctor logic here */ }

#if NET8_0_OR_GREATER
    [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor
#endif
    protected MyException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        /* your rehydration logic here */
    }

#if NET8_0_OR_GREATER
    [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to GetObjectData
#endif
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        /* your dehydration here */

        base.GetObjectData(info, context);
    }
}

If you're writing your own standalone serializable type

If you're declaring a custom type which is marked [Serializable] or which implements the ISerializable interface, you should not see any compilation warnings as a result of this change unless you've implemented a less common code pattern. For example, the code below should not see any warnings.

[Serializable]
public class Person
{
    private string name;
    private DateTime dateOfBirth;

    public string Name { get { return name; } set { name = value; } }
    public DateTime DateOfBirth { get { return dateOfBirth; } set { dateOfBirth = value; } }
}

However, .NET recommends against annotating your types in this manner, as modern serialization libraries do not require such annotations. Instead, consider leaving off the [Serializable] attribute and the ISerializable interface, instead relying on your serialization library to access your object through its public properties rather than its private fields.

// [Serializable] <-- suggest removing this attribute
public class Person
{
    private string name;
    private DateTime dateOfBirth;

    public string Name { get { return name; } set { name = value; } }
    public DateTime DateOfBirth { get { return dateOfBirth; } set { dateOfBirth = value; } }
}

If you're subclassing an existing .NET type which is marked [Serializable] and you're observing SYSLIB0051 warning codes, see the earlier section on creating custom exception types. The overall pattern described there remains generally applicable.

If you're writing a serialization library

Microsoft strongly recommends against serialization libraries supporting the legacy serialization infrastructure ([Serializable] and ISerializable). Modern serialization libraries should have policy based on a type's public APIs rather than its private implementation details. Basing a serializer on these implementation details and strongly tying it to ISerializable and other mechanisms which encourage embedding type names within the serialized payload can lead to many of the same problems called out in https://aka.ms/binaryformatter.

If your serialization library must remain compatible with the legacy serialization infrastructure, you can easily suppress the legacy serialization API obsoletions (the SYSLIB0050 category) project-wide by putting the following block in your .csproj, .vbproj, or directory-level .props file.

<PropertyGroup>
  <NoWarn>$(NoWarn);SYSLIB0050</NoWarn>
</PropertyGroup>

Feature area

Serialization

Affected APIs

The following types are marked obsolete as warning with obsoletion code SYSLIB0050.

The following APIs are marked obsolete as warning with obsoletion code SYSLIB0050.

The following APIs are marked obsolete as warning with obsoletion code SYSLIB0051.

The following types are marked [EditorBrowsable(EditorBrowsableState.Never)], but they are not obsoleted. Marking them with the [EditorBrowsable(EditorBrowsableState.Never)] annotation will hide them from within the Visual Studio IDE. However, they can still be referenced by .NET 8 apps, and callers will not observe a compilation warning or error when referencing these types.


Associated WorkItem - 91242

Metadata

Metadata

Assignees

Labels

🏁 Release: .NET 8Work items for the .NET 8 release📌 seQUESTeredIdentifies that an issue has been imported into Quest.breaking-changeIndicates a .NET Core breaking changesource incompatibleSource code may encounter a breaking change in behavior when targeting the new version.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions