All classes whose instances must / can be stored via BinaryFormater must have this very same [Serializable] attribute. Why is it needed?

No, of course, it is clear that he says the CLR environment, they say, the type is serializable, therefore, be good (medium) and serialize!

In my view, almost on the fingers , this attribute helps MSV to add and implement the GetObjectData(SerializationInfo info, StreamingContext context) from the ISerializable interface for each field in the class. For example:

  public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", name); info.AddValue("BDate", BDate); info.AddValue("Price", Price); } 

And implement a constructor for deserialization, like this:

  public Engineer(SerializationInfo info, StreamingContext context) : base(info.GetString("Name"), info.GetDateTime("BDate")) { Price = info.GetDecimal("Price"); } 

BUT, even if you implemented the ISerializable interface, the attribute is still needed. CA2237: MarkISerializableTypesWithSerializable

What is he doing all the same, so to speak, under the hood ?

  • You can see the source of this attribute. He does not do anything under the hood. - Alexander Petrov
  • 2
    @AlexanderPetrov: Well, all attributes are arranged (almost?). An attribute is just a label attached to a class that can be read from another code. - VladD
  • This is just a token, but in general attributes can perform more essential roles of the ServiceContractAttribute , you can also hang a lot of things on custom attributes, but this is a separate zen. Well, if my memory does not fail me, the class may not implement ISerializable, but to be serializable - rdorn

4 answers 4

In addition to the answer @Alexander Petrov , I will quote MSDN (in my own translation):

Use the SerializableAttribute attribute on a type to indicate that instances of this type are allowed to be serialized. If at least one type in the graph of the objects being serialized ( that is, the type of the root object or any object that it directly or indirectly refers to inside ) does not have this attribute, the CLR will throw a SerializationException .

This prevents errors related to the unintended serialization of types that are not intended for serialization.

Why is this necessary for binary serialization? The fact is that binary serialization breaks encapsulation and works directly with closed fields. At the same time, unlike calling the setter for a public property, the type invariant can easily be broken. For example, if each copy of a class gets a unique number when creating it, then binary deserialization will clone this same number, thereby violating the logic of the program.

To avoid such problems, for the type to participate in serialization, the CLR requires a conscious decision by the programmer that each individual can be safely serialized.

(Information partially taken from this answer .)

    Searching for explanations and thinking carefully, I finally became entrenched in the opinion that I had long suspected: this attribute serves as a hint not so much for a BinaryFormatter as for a person. Do not serialize anything!

    For example, when developing on Windows Forms (when all the logic is in button clicks, and the data is in the form fields), newbies often have the temptation to retain the appearance and, in general, all program data by serializing the main form. And what, just the same! However, if this were implemented, then this would lead to the preservation of hundreds, if not thousands, of mostly empty (unchanged from the default values) form properties with all embedded controls (including images). Which, of course, is redundant and unnecessary.

    Accordingly, when a developer writes a class that contains fields / properties with data, it must determine whether it is possible (necessary) to freely (de) serialize it and flag it with this attribute if necessary. Another developer using this class, by the presence / absence of the attribute, will understand what can be done with it. If necessary, he will have to create a class specifically designed for storing individual (and only necessary!) Data.

    • There is another problem with Forms, pictures and empty controls are not so scary, but the fact that even a form with a single control forms a cyclic graph on it (a collection of child controls and each has a backward reference to the parent) will lead to endless recursion during serialization, and will naturally fall out in StackOverflowException. And the form is not the worst case in this regard. - rdorn

    I will add some more information to the reflection.

    A class can be serializable and without implementing an ISerializable interface ISerializable

     [Serializable] class A { public int Value; } 

    In this case serialization will be applied by default, i.e. public fields and properties will be serialized. But it is necessary to take into account that for subsequent deserialization a class must have a default constructor (without parameters), otherwise an exception will be thrown.

    In special cases, both an attribute and an interface implementation are required. We omit the case when the developer needs to control the process based on considerations of class logic and take an example where this is necessary regardless of the developer’s desire.

     [Serializable] class B { public B Pred; public B Next; } 

    By default, if during serialization there is a public field containing an object, this object is also serialized. What this leads to: in the example class above, this will lead to infinite recursion and throw a StackOverflowException if there are two or more related objects of class B since each object has a link to the neighbor, and that, in turn, has a reverse link. In this case, this class must implement the ISerializable interface, despite the ISerializable simplicity and lack of instructions from the compiler.

    Ok, the doubly linked list is immediately visible. Take a more complex example:

     [Serializable] class A { public B Item; } [Serializable] class B { public C Item; } [Serializable] class C { public A Item; } 

    Serialization of any of these objects can lead to a StackOverflowException if objects stored in the Item properties form a closed chain. Despite the simplicity of the example, similar constructions are quite common and the presence of a cyclic object graph may be far from obvious.

    Since attributes are not inherited and it is required that all objects involved in serialization have the type marked with the Serializable attribute, the chance to accidentally serialize an object that is part of a cycle and thus cause a stack overflow is minimized, except for the cases of synthetic examples as given above, and explicit errors (sabotage?) programmer marked the attribute Serializable all that came handy.

    • For binary serialization, no default constructor is needed. To create an object to bypass the constructor, use FormatterServices.GetUninitializedObject . Also, in order not to serialize the same object several times and, accordingly, the ObjectIDGenerator class is not concerned about circular references. - PetSerAl

    Bidirectional lists, including circular ones, are perfectly serialized. Here is a class that is cast as the one that throws a StackOverFlowException, serialized! Without user serialization and other tricks. What do you think collections are serialized? And they are all Serializable! Now I wrote a class like this:

     [Serializable] public sealed class MyObjectCircle { public int Data; public MyObjectCircle Next = null; public MyObjectCircle Prev = null; } 

    Of course, I filled it with data, like this:

      var obj = new MyObjectCircle(data1); var objNext = new MyObjectCircle(data2, null, obj); var objPrev = new MyObjectCircle(data3, obj); obj.Next = objNext; obj.Prev = objPrev; 

    And what, he does not serialize? Serialized, Binary formatter, as it should. Deserialized too without problems.

     private void BtSerialize_Click ( object sender, EventArgs e ) { int.TryParse ( textBox6.Text, out var data1 ); int.TryParse(textBox7.Text, out var data2); int.TryParse(textBox8.Text, out var data3); var obj = new MyObjectCircle(data1); var objNext = new MyObjectCircle(data2, null, obj); var objPrev = new MyObjectCircle(data3, obj); obj.Next = objNext; obj.Prev = objPrev; var formatter = new BinaryFormatter ( ); Stream stream = new FileStream ( "serializing.bin", FileMode.Create ); formatter.Serialize ( stream, obj ); stream.Close ( ); TBInfo.Text = @"Сериализовано в ""serializing.bin"""; } } 

    Seriously, why does a serializer need to serialize something that is already serialized?

    I hope I understand the rdorn comment correctly.