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 change]: Legacy serialization infrastructure APIs marked obsolete #34893

Closed
1 of 3 tasks
GrabYourPitchforks opened this issue Apr 5, 2023 · 5 comments · Fixed by #35217
Closed
1 of 3 tasks
Assignees
Labels
breaking-change Indicates a .NET Core breaking change 🏁 Release: .NET 8 Work items for the .NET 8 release doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 📌 seQUESTered Identifies that an issue has been imported into Quest. source incompatible Source code may encounter a breaking change in behavior when targeting the new version.

Comments

@GrabYourPitchforks
Copy link
Member

GrabYourPitchforks commented Apr 5, 2023

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

@GrabYourPitchforks GrabYourPitchforks added doc-idea Indicates issues that are suggestions for new topics [org][type][category] breaking-change Indicates a .NET Core breaking change Pri1 High priority, do before Pri2 and Pri3 labels Apr 5, 2023
@dotnet-bot dotnet-bot added ⌚ Not Triaged Not triaged source incompatible Source code may encounter a breaking change in behavior when targeting the new version. labels Apr 5, 2023
@gewarren gewarren added 🏁 Release: .NET 8 Work items for the .NET 8 release and removed ⌚ Not Triaged Not triaged labels Apr 5, 2023
@jeffhandley
Copy link
Member

@GrabYourPitchforks Should we check the 'Behavioral change' checkbox too since running an existing library in an updated runtime will yield different behavior?

@GrabYourPitchforks
Copy link
Member Author

@GrabYourPitchforks Should we check the 'Behavioral change' checkbox too since running an existing library in an updated runtime will yield different behavior?

@jeffhandley I had opened a different issue #34891 for that. One issue tracks the behavioral change; one issue tracks the new obsoletions. I did it this way because each issue requires its own resolution strategy, and most app authors only need one or the other (but not both). If this is confusing we could certainly combine the issues!

@jeffhandley
Copy link
Member

Ah, gotcha! Thanks for the clarification.

@jeffhandley
Copy link
Member

These changes will go into effect in .NET 8 Preview 4. At that time, folks are likely to observe SYSLIB0050 and SYSLIB0051 diagnostic messages in their builds. Those diagnostic messages will have a link to this issue to provide more context until we reach the point of having the final documentation written to capture this content.

Since we will be routing folks to this issue, but we won't be actively monitoring this issue for comments, we are going to lock the conversation.

If you experience an issue that is not already covered in the description of these changes, please file a new issue in the dotnet/runtime repository.

@dotnet dotnet locked as resolved and limited conversation to collaborators Apr 11, 2023
@gewarren gewarren added the in-pr This issue will be closed (fixed) by an active pull request. label May 9, 2023
@ghost ghost removed the in-pr This issue will be closed (fixed) by an active pull request. label May 16, 2023
@gewarren gewarren added the 🗺️ reQUEST Triggers an issue to be imported into Quest. label May 18, 2023
@github-actions github-actions bot added 📌 seQUESTered Identifies that an issue has been imported into Quest. and removed 🗺️ reQUEST Triggers an issue to be imported into Quest. labels May 19, 2023
@kartheekp-ms
Copy link
Contributor

kartheekp-ms commented Nov 9, 2023

In this case, I think CA2237: Mark ISerializable types with SerializableAttribute and CA2229: Implement serialization constructors analyzers are not needed anymore. Created an issue dotnet/roslyn-analyzers#7026

SimonGeering added a commit to SimonGeering/AdminAssistant that referenced this issue Nov 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
breaking-change Indicates a .NET Core breaking change 🏁 Release: .NET 8 Work items for the .NET 8 release doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 📌 seQUESTered Identifies that an issue has been imported into Quest. source incompatible Source code may encounter a breaking change in behavior when targeting the new version.
Projects
No open projects
Status: Done
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants