Cómo no pegarse un tiro en el pie cuando se trabaja con serialización

Cómo no pegarse un tiro en el pie cuando se trabaja con serialización

A pesar de que es bastante fácil y cómodo usar el mecanismo de serialización en C#, hay algunos puntos que vale la pena tomar en cuenta. Este artículo trata sobre las formas en que podría dispararse a sí mismo trabajando con serialización, ejemplos de código, dónde se encuentran los principales obstáculos y también sobre la forma en que PVS-Studio puede ayudarlo a evitar meterse en problemas.

¿Para quién es este artículo?

Este artículo será especialmente útil para aquellos que recién comienzan a familiarizarse con el mecanismo de serialización. Los programadores más experimentados también pueden aprender algo interesante, o simplemente estar seguros de que incluso los profesionales cometen errores.

Sin embargo, se supone que el lector ya está algo familiarizado con el mecanismo de serialización.

Pero, ¿qué tiene que ver PVS-Studio con esto? En la versión 6.05, agregamos 6 reglas de diagnóstico que detectan código sospechoso mediante el mecanismo de serialización. Estos diagnósticos buscan principalmente áreas problemáticas relacionadas con el [Serializable] atributo, o la implementación de ISerializable interfaz.

Nota.

Debemos entender que las declaraciones descritas en el artículo son relevantes para algunos serializadores, por ejemplo - BinaryFormatter y SoapFormatter; para otros, que son serializadores escritos manualmente, el comportamiento puede ser diferente. Por ejemplo, la ausencia del atributo [Serializable] porque la clase puede no impedir la serialización y deserializarla con un serializador personalizado.

Por cierto, si está trabajando con serialización, le aconsejo que descargue la versión de prueba del analizador y verifique su código para ver fragmentos sospechosos.

Implementando ISerializable, no se olvide del constructor de serialización

La implementación del tipo de ISerializable La interfaz ayuda a controlar la serialización, eligiendo qué miembros deben serializarse, cuáles no, qué valores deben escribirse durante la serialización de los miembros, etc.

ISerializable la interfaz contiene una declaración de un método - GetObjectData, que será llamado a la serialización del objeto. Pero junto con este método, siempre deberíamos tener implementado un constructor que se llamará cuando se deserialice el objeto. Como la interfaz no puede obligarte a implementar un constructor en la clase, esta tarea va al programador que está realizando la serialización del tipo serializable. El constructor de serialización tiene la siguiente firma:

Ctor(SerializationInfo, StreamingContext)

Sin este constructor, la serialización del objeto será exitosa (suponiendo que GetObjectData se implementa correctamente), pero será imposible restaurarlo (deserializarlo); tendremos la excepción SerializationException lanzado.

Veamos un ejemplo de dicho código de un proyecto de Glimpse:

[Serializable]
internal class SerializableTestObject : ISerializable
{
  public string TestProperty { get; set; }

  public void GetObjectData(SerializationInfo info, 
                            StreamingContext context)
  {
    info.AddValue("TestProperty", this.TestProperty);
  }
}

Advertencia de PVS-Studio: V3094 Posible excepción al deserializar. Falta el constructor SerializableTestObject(SerializationInfo, StreamingContext). Glimpse.Test.AspNet SessionModelConverterShould.cs 111

La serialización del ítem de esta clase será exitosa, pero durante la deserialización tendremos una excepción, porque no hay un constructor apropiado. Lo más probable es que no se trate de un error (a juzgar por la clase y el nombre del archivo), pero como ilustración de la situación, funciona bien.

El constructor de serialización para esta clase podría verse así:

protected SerializableTestObject(SerializationInfo info, 
                                 StreamingContext context)
{
  TestProperty = info.GetString(nameof(TestProperty));
}

Preste atención al modificador de acceso del constructor de serialización

Al escribir un tipo que implementa ISerializable interfaz es muy importante definir el modificador de acceso para el constructor de serialización. Hay varias formas posibles:

  • el constructor de serialización se declara con private modificador en una clase sin sellar;
  • el constructor de serialización se declara con un modificador de acceso public o interno;
  • el constructor de serialización se declara con protected modificador en una clase sellada.

La primera variante es la que más nos interesa, ya que puede ser la más peligrosa. Veamos brevemente el segundo punto, el tercero no es tan útil:el compilador no declarará el miembro con el protegido modificador en la estructura (error de compilación), si esta clase se declara en la clase sellada, el compilador emitirá una advertencia.

El constructor de serialización en una clase sin sellar tiene un modificador de acceso 'privado'

Este es el tipo de situación más peligroso, donde los modificadores de acceso se aplican incorrectamente a los constructores de serialización. Si el tipo no está sellado, se da a entender que puede tener descendientes. Sin embargo, si el constructor de serialización tiene un privado modificador de acceso, no se puede llamar desde una clase secundaria.

En este caso, el desarrollador de la clase secundaria tiene 2 opciones:no usar la clase principal o deserializar manualmente los miembros de una clase base. Vale la pena señalar que el segundo caso difícilmente puede considerarse una solución al problema:

  • no hay certeza de que se proporcione una deserialización de miembros triviales en la clase base;
  • el desarrollador de la clase secundaria puede olvidarse de deserializar un miembro de la clase base;
  • A pesar de querer hacerlo, será imposible deserializar miembros privados de la clase base.

Por lo tanto, al escribir una clase serializable sin sellar, preste atención al modificador de acceso que tiene el constructor de serialización.

Durante el análisis encontramos varios proyectos en los que no se cumplía esta regla.

NHibernate

[Serializable]
public class ConnectionManager : ISerializable, 
                                 IDeserializationCallback
{
  ....
  private ConnectionManager(SerializationInfo info, 
                            StreamingContext context)
  {
    ....
  }
  ....
}

Advertencia de PVS-Studio:V3103 No se podrá acceder a un constructor Ctor privado (SerializationInfo, StreamingContext) en tipo sin sellar al deserializar tipos derivados. NHibernate ConnectionManager.cs 276

Roslyn

[Serializable]
private class TestDiagnostic : Diagnostic, ISerializable
{
  ....
  private TestDiagnostic (SerializationInfo info, 
                          StreamingContext context)
  {
    ....
  }
  ....
}

Advertencia de PVS-Studio: V3103 No se podrá acceder a un constructor TestDiagnostic(SerializationInfo, StreamingContext) privado en tipo sin sellar al deserializar tipos derivados. DiagnosticAnalyzerTests.cs 100

En ambos ejemplos, dados anteriormente, el desarrollador debería haber configurado el modificador de acceso protegido para el constructor de serialización, de modo que las clases secundarias puedan llamarlo durante la deserialización.

No declare el constructor de serialización con modificadores 'público' o 'interno'

Este es un consejo de "buen estilo de codificación". La declaración del constructor de serialización con el modificador public o interno no generará un error, pero no tiene sentido hacer esto:este constructor no está diseñado para usarse externamente y no hay diferencia para el serializador, cuyo modificador de acceso tiene el constructor.

Al revisar los proyectos de código abierto, vimos varios casos en los que esta regla no se tuvo en cuenta.

MSBuild

[Serializable]
private sealed class FileState : ISerializable
{
  ....
  internal SystemState(SerializationInfo info, 
                       StreamingContext context)
  {
    ....
  }
  ....
}

Advertencia de PVS-Studio: V3103 El constructor Ctor(SerializationInfo, StreamingContext) debe usarse para la deserialización. No se recomienda hacerlo interno. Considera hacerlo privado. Microsoft.Build.Tasks SystemState.cs 218

[Serializable]
private sealed class FileState : ISerializable
{
  ....
  internal FileState(SerializationInfo info, StreamingContext context)
  {
    ....
  }
  ....
}

Advertencia de PVS-Studio: V3103 El constructor Ctor(SerializationInfo, StreamingContext) debe usarse para la deserialización. No se recomienda hacerlo interno. Considera hacerlo privado. Microsoft.Build.Tasks SystemState.cs 139

En ambos casos, el modificador de acceso privado debería haberse establecido para el constructor de serialización, porque ambas clases están selladas.

NHibernate

[Serializable]
public class StatefulPersistenceContext : IPersistenceContext,   
                                          ISerializable, 
                                          IDeserializationCallback
{
  ....
  internal StatefulPersistenceContext(SerializationInfo info, 
                                      StreamingContext context)
  {
    ....
  }
  ....
}

Advertencia de PVS-Studio: V3103 El constructor Ctor(SerializationInfo, StreamingContext) debe usarse para la deserialización. No se recomienda hacerlo interno. Considere hacerlo protegido. NHibernate StatefulPersistenceContext.cs 1478

[Serializable]
public class Configuration : ISerializable
{
  ....
  public Configuration(SerializationInfo info, 
                       StreamingContext context)
  {
   ....
  }
  ....
}

Advertencia de PVS-Studio: V3103 El constructor Ctor(SerializationInfo, StreamingContext) debe usarse para la deserialización. No se recomienda hacerlo público. Considere hacerlo protegido. Configuración de NHibernate.cs 84

Teniendo en cuenta el hecho de que ambas clases no están selladas, deberíamos haber establecido protected como modificador de acceso para los constructores de serialización.

Implementar método virtual GetObjectData en clases abiertas

La regla es simple:cuando escribe una clase sin sellar, implementa el ISerializable interfaz, declare el método GetObjectData con la virtual modificador Esto permitirá que las clases secundarias realicen la serialización correcta del objeto al usar polimorfismo.

Para ver la situación más claramente, sugiero echar un vistazo a varios ejemplos.

Supongamos que tenemos las siguientes declaraciones de las clases padre e hijo.

[Serializable]
class Base : ISerializable
{
  ....
  public void GetObjectData(SerializationInfo info, 
                            StreamingContext context)
  {
    ....
  }
}

[Serializable]
sealed class Derived : Base
{
  ....
  public new void GetObjectData(SerializationInfo info, 
                                StreamingContext context)
  {
    ....
  }
}

Supongamos que tenemos un método de serialización y deserialización de un objeto:

void Foo(BinaryFormatter bf, MemoryStream ms)
{
  Base obj = new Derived();
  bf.Serialize(ms, obj);
  ms.Seek(0, SeekOrigin.Begin);
  Derived derObj = (Derived)bf.Deserialize(ms);
}

En este caso, la serialización se realizará incorrectamente porque GetObjectData El método no se llamará para la clase principal, sino para la clase secundaria. En consecuencia, los miembros de la clase secundaria no se serializarán. Si durante la deserialización del objeto de SerializationInfo obtenemos los valores de los miembros, agregados en el método GetObjectData de la clase secundaria, lanzaremos una excepción, como el objeto de SerializationInfo type no contendrá las claves requeridas.

Para corregir un error en la clase principal de GetObjectData método, debemos agregar el virtual modificador, en una clase derivada - override .

Pero, si en la clase principal solo hay una implementación explícita de ISerializable interfaz, no podrá agregar una virtual modificador Sin embargo, si deja todo como está, corre el riesgo de complicar la vida de los desarrolladores de las clases secundarias.

Veamos un ejemplo de implementación de las clases padre e hijo:

[Serializable]
class Base : ISerializable
{
  ....
  void ISerializable.GetObjectData(SerializationInfo info, 
                                   StreamingContext context)
  {
    ....
  }
}

[Serializable]
sealed class Derived : Base, ISerializable
{
  ....
  public void GetObjectData(SerializationInfo info, 
                            StreamingContext context)
  {
    ....
  }
}

En este caso, no podremos acceder a GetObjectData método de la clase padre de la clase hijo. Además, si tenemos miembros privados serializados en el método base, no será posible acceder a ellos desde una clase secundaria, lo que significa que tampoco podremos tener una serialización correcta. Para corregir este error, debemos agregar una implementación implícita a una clase base de un método virtual GetObjectData , además de la implementación explícita. Entonces el código corregido podría verse así:

[Serializable]
class Base : ISerializable
{
  ....
  void ISerializable.GetObjectData(SerializationInfo info, 
                                    StreamingContext context)
  {
    GetObjectData(info, context);
  }

  public virtual void GetObjectData(SerializationInfo info, 
                                    StreamingContext context)
  {
    ....
  }
}

[Serializable]
sealed class Derived : Base
{
  ....
  public override void GetObjectData(SerializationInfo info, 
                                     StreamingContext context)
  {
    ....
    base.GetObjectData(info, context);
  }
}

O, si no queremos hacer la herencia de esta clase, deberíamos hacerla sellada, agregando un sellado modificador de la declaración de clase.

Roslyn

[Serializable]
private class TestDiagnostic : Diagnostic, ISerializable
{
  private readonly string _kind;
  ....
  private readonly string _message;
  ....
  void ISerializable.GetObjectData(SerializationInfo info,  
                                   StreamingContext context)
  {
    info.AddValue("id", _descriptor.Id);
    info.AddValue("kind", _kind);
    info.AddValue("message", _message);
    info.AddValue("location", _location, typeof(Location));
    info.AddValue("severity", _severity, typeof(DiagnosticSeverity));
    info.AddValue("defaultSeverity", _descriptor.DefaultSeverity,
                   typeof(DiagnosticSeverity));
    info.AddValue("arguments", _arguments, typeof(object[]));
  }
  ....
}

Advertencia de PVS-Studio: La implementación de V3104 'GetObjectData' en el tipo sin sellar 'TestDiagnostic' no es virtual, es posible una serialización incorrecta del tipo derivado. CSharpCompilerSemanticTest DiagnosticAnalyzerTests.cs 112

Diagnóstico de prueba no está sellado (aunque es privado, por lo que puede haber herencia de él en los marcos de la misma clase), pero con eso, solo tiene una implementación explícita de ISerializable interfaz, en el que tenemos los miembros privados serializados. Esto significa lo siguiente:el desarrollador de la clase secundaria no podrá serializar los miembros necesarios:el método GetObjectData no está disponible y el modificador de acceso no permitirá el acceso directo a los miembros.

Sería mejor mover todo el código de serialización, dado anteriormente, al método virtual GetObjectData , y usarlo desde la implementación de la interfaz explícita.

void ISerializable.GetObjectData(SerializationInfo info, 
                                 StreamingContext context)
{
  GetObjectData(info, context);
}

public virtual void GetObjectData(SerializationInfo info,
                                  StreamingContext context)
{
  info.AddValue("id", _descriptor.Id);
  info.AddValue("kind", _kind);
  info.AddValue("message", _message);
  info.AddValue("location", _location, typeof(Location));
  info.AddValue("severity", _severity, typeof(DiagnosticSeverity));
  info.AddValue("defaultSeverity", _descriptor.DefaultSeverity,
                typeof(DiagnosticSeverity));
  info.AddValue("arguments", _arguments, typeof(object[]));
}

Todos los miembros serializables deben tener un tipo serializable

Esta condición es obligatoria para la correcta serialización de un objeto, independientemente de si se trata de una serialización automática (cuando el tipo se anota con el [Serializable] atributo, y cuando no implementa el ISerializable interfaz), o la serialización se realiza manualmente (ISerializable implementado).

De lo contrario, si durante la serialización tenemos un miembro que no está anotado con el [Serializable] atributo, lanzaremos la excepción de SerializationException tipo.

Si desea serializar un objeto sin que los miembros tengan un tipo no serializable, existen varias variantes posibles:

  • hacer serializable un tipo no serializable;
  • si hay serialización automática, anote los campos que no deben serializarse con un atributo [NonSerialized];
  • si realiza una serialización manual, simplemente ignore los miembros que no necesita.

Preste atención al hecho de que [NonSerialized] El atributo solo se puede aplicar a los campos. Por lo tanto, no podrá evitar la serialización de una propiedad, pero, si tiene un tipo no serializable, obtendrá una excepción. Por ejemplo, al intentar serializar SerializedClass , la definición se da a continuación:

sealed class NonSerializedType { }

[Serializable]
sealed class SerializedClass
{
  private Int32 value;
  public NonSerializedType NSProp { get; set; }
}

Solucionamos esta situación implementando una propiedad a través de un campo, anotado por un atributo [NonSerialized] :

[Serializable]
sealed class SerializedClass
{
  private Int32 value;

  [NonSerialized]
  private NonSerializedType nsField;

  public NonSerializedType NSProp
  {
    get { return nsField; }
    set { nsField = value; }
  }
}

La regla de diagnóstico V3097 del analizador estático de PVS-Studio puede detectar errores como el tipo serializable que tiene miembros de tipos no serializables, no anotados por [NonSerialized] atributo.

Pero nuevamente, debo mencionar que esta advertencia no siempre detecta un error real; todo dependerá del serializador que se esté utilizando.

Echemos un vistazo a los fragmentos de código donde se violó esta condición.

Subtexto

public class BlogUrlHelper
{
  ....
}

[Serializable]
public class AkismetSpamService : ICommentSpamService
{
  ....
  readonly BlogUrlHelper _urlHelper;
  ....
}

Advertencia de PVS-Studio: V3097 Posible excepción:el tipo 'AkismetSpamService' marcado con [Serializable] contiene miembros no serializables que no están marcados con [NonSerialized]. Subtexto.Framework AkismetSpamService.cs 31

El tipo BlogUrlHelper del archivado _urlHelper no es serializable, por lo que si intenta serializar la instancia de AkismetSpamService con algunos serializadores, obtendremos la excepción de SerializationException tipo lanzado. Debemos resolver el problema basándonos en la situación. Si usa serializadores de BinaryFormatter o SoapFormatter type - es necesario anotar el campo con el atributo [NonSerialized] o anotar el BlogUrlHepler escriba con [Serializable] atributo. Si usa otros serializadores que no requieren [Serializable] atributo en los campos serializables, entonces es mucho más simple.

NHibernate

public class Organisation
{
 ....
}

[Serializable]
public class ResponsibleLegalPerson  
{
  ....
  private Organisation organisation;
  ....
}

Advertencia de PVS-Studio: V3097 Posible excepción:el tipo 'ResponsibleLegalPerson' marcado con [Serializable] contiene miembros no serializables que no están marcados con [NonSerialized]. NHibernate.Test ResponsableLegalPerson.cs 9

La situación es la misma que la anterior:es todo o nada. Todo depende del serializador.

No olvide el atributo [Serializable] al implementar la interfaz ISerializable

Este consejo se aplica a aquellos que recién comienzan a trabajar con la serialización. Controlando la serialización manualmente, implementando el ISerializable interfaz, es fácil olvidar anotar el tipo con [Serializable], lo que potencialmente puede conducir a la excepción de SerializationException escribe. Serializadores como BinaryFormatter requieren tal atributo.

Desarrollo sostenido

Un ejemplo interesante de este error en el proyecto SharpDevelop.

public class SearchPatternException : Exception, ISerializable
{
  ....
  protected SearchPatternException(SerializationInfo info, 
                                   StreamingContext context) 
    : base(info, context)
  {
  }
}

Advertencia de PVS-Studio: V3096 Posible excepción al serializar el tipo 'SearchPatternException'. Falta el atributo [Serializable]. ICSharpCode.AvalonEdit ISearchStrategy.cs 80

public class DecompilerException : Exception, ISerializable
{
  ....
  protected DecompilerException(SerializationInfo info, 
                                StreamingContext context) 
    : base(info, context)
  {
  }
}

Advertencia de PVS-Studio: V3096 Posible excepción al serializar el tipo 'DecompilerException'. Falta el atributo [Serializable]. ICSharpCode.Decompiler DecompilerException.cs 28

Para pasar el objeto de excepción entre los dominios de la aplicación, tenemos su serialización y deserialización. En consecuencia, los tipos de excepción deben ser serializables. En los ejemplos anteriores, los tipos SearchPatternException y DecompilerException, se heredan de Exception e implementar constructores de serialización, pero al mismo tiempo no están anotados por [Serializable] atributo, lo que significa que al intentar serializar objetos de este tipo (por ejemplo, para transferir entre los dominios), tendremos una excepción de SerializationException tipo generado. Por lo tanto, por ejemplo, al lanzar una excepción en otro dominio de aplicación, no detectará la excepción lanzada, sino SerializationException .

Asegúrese de que en GetObjectData, todos los miembros de tipo necesarios se serialicen

Implementando el ISerializable interfaz, y definiendo el GetObjectData método, asume la responsabilidad de los miembros del tipo que se serializarán y los valores que se escribirán allí. En este caso, a los desarrolladores se les ofrece un gran alcance en la gestión de la serialización:como valor serializable, asociado con el miembro (para ser honesto, con cualquier cadena), puede escribir el valor real del objeto serializado, el resultado del trabajo de algún método, constante o valor literal, lo que quieras.

Sin embargo, en este caso, una gran responsabilidad recae sobre los hombros del desarrollador, ya que debe recordar todos los miembros que se pretende serializar, incluso si están en la clase base. Todos somos simples seres humanos, por lo que a veces algunos miembros quedan olvidados.

Hay una regla especial V3099 en el analizador PVS-Studio para detectar tales situaciones. Sugiero mirar algunos ejemplos de código que fueron detectados por esta regla.

Desarrollo sostenido

[Serializable]
public abstract class XshdElement
{
  public int LineNumber { get; set; }
  
  public int ColumnNumber { get; set; }
  
  public abstract object AcceptVisitor(IXshdVisitor visitor);
}

[Serializable]
public class XshdColor : XshdElement, ISerializable
{
  ....
  public virtual void GetObjectData(SerializationInfo info,        
                                    StreamingContext context)
  {
    if (info == null)
      throw new ArgumentNullException("info");
    info.AddValue("Name", this.Name);
    info.AddValue("Foreground", this.Foreground);
    info.AddValue("Background", this.Background);
    info.AddValue("HasUnderline", this.Underline.HasValue);
    if (this.Underline.HasValue)
      info.AddValue("Underline", this.Underline.Value);
    info.AddValue("HasWeight", this.FontWeight.HasValue);
    if (this.FontWeight.HasValue)
      info.AddValue("Weight", this.FontWeight
                                  .Value
                                  .ToOpenTypeWeight());
    info.AddValue("HasStyle", this.FontStyle.HasValue);
    if (this.FontStyle.HasValue)
      info.AddValue("Style", this.FontStyle.Value.ToString());
    info.AddValue("ExampleText", this.ExampleText);
  }
}

Advertencia de PVS-Studio: V3099 No todos los miembros del tipo 'XshdColor' están serializados dentro del método 'GetObjectData':LineNumber, ColumnNumber. ICSharpCode.AvalonEdit XshdColor.cs 101

En este código no hay problemas descritos anteriormente, como modificadores de acceso incorrectos en el constructor de serialización o la falta de [Serializable] atributo, o virtual modificador para GetObjectData método.

Por desgracia, todavía hay un error aquí. En el GetObjectData método, las propiedades de la clase base no se tienen en cuenta, lo que significa que algunos datos se perderán durante la serialización. Como resultado, durante la deserialización, se restaurará un objeto con un estado diferente.

En este caso, la solución es agregar manualmente los valores necesarios, como sigue, por ejemplo:

info.AddValue(nameof(LineNumber), LineNumber);
info.AddValue(nameof(ColumnNumber), ColumnNumber);

Si la clase base también hubiera implementado el ISerializable interfaz, la solución habría sido más elegante:la llamada en el método derivado GetObjectData de la base.

NHibernate

[Serializable]
public sealed class SessionImpl : AbstractSessionImpl, 
                                  IEventSource, 
                                  ISerializable, 
                                  IDeserializationCallback
{
  ....
  void ISerializable.GetObjectData(SerializationInfo info, 
                                   StreamingContext context)
  {
    log.Debug("writting session to serializer");

    if (!connectionManager.IsReadyForSerialization)
    {
      throw new InvalidOperationException("Cannot serialize a Session 
                                           while connected");
    }

    info.AddValue("factory", Factory, typeof(SessionFactoryImpl));
    info.AddValue("persistenceContext", persistenceContext, 
                   typeof(StatefulPersistenceContext));
    info.AddValue("actionQueue", actionQueue, typeof(ActionQueue));
    info.AddValue("timestamp", timestamp);
    info.AddValue("flushMode", flushMode);
    info.AddValue("cacheMode", cacheMode);

    info.AddValue("interceptor", interceptor, typeof(IInterceptor));

    info.AddValue("enabledFilters", enabledFilters, 
                   typeof(IDictionary<string, IFilter>));
    info.AddValue("enabledFilterNames", enabledFilterNames, 
                   typeof(List<string>));

    info.AddValue("connectionManager", connectionManager, 
                   typeof(ConnectionManager));
  }
  .... 
  private string fetchProfile;
  ....
}

Advertencia de PVS-Studio: V3099 No todos los miembros del tipo 'SessionImpl' se serializan dentro del método 'GetObjectData':fetchProfile. NHibernate SessionImpl.cs 141

Esta vez el campo de la clase actual (fetchProfile ) ha sido olvidado para ser serializado. Como puede ver en la declaración, no está anotado por [NonSerialized] atributo (en contraste con los otros campos, que no son serializables en el GetObjectData método).

Había dos fragmentos más similares en el proyecto:

  • V3099 No todos los miembros del tipo 'Configuración' se serializan dentro del método 'GetObjectData':currentDocumentName, preMappingBuildProcessed. Configuración de NHibernate.cs 127
  • V3099 No todos los miembros del tipo 'ConnectionManager' se serializan dentro del método 'GetObjectData':flushingFromDtcTransaction. NHibernate ConnectionManager.cs 290

Hay algo bastante interesante acerca de los errores de este tipo:conducen al lanzamiento de una excepción o a errores lógicos que son realmente difíciles de detectar.

La excepción se producirá en el caso de que, en el constructor de serialización, el programador intente obtener el valor del campo que se acaba de agregar (y acceder mediante la clave que falta). Si el miembro se olvidó por completo (tanto en el GetObjectData y en el constructor de serialización), el estado del objeto se dañará.

Resumen

Resumiendo brevemente toda la información, podemos formular varios consejos y reglas:

  • Anotar los tipos, implementando el ISerializable interfaz con el [Serializable] atributo.
  • Asegúrese de que todos los miembros anotados por [Serializable] el atributo se serializa correctamente;
  • Implementación de ISerializable interfaz, no olvide implementar el constructor de serialización (Ctor(SerializationInfo, StreamingContext) );
  • En los tipos sellados, establezca el modificador de acceso privado para un constructor de serialización, en el sin sellar - protegido;
  • En los tipos abiertos que implementan ISerializable interfaz, haga el GetObjectData método virtual;
  • Compruebe eso en GetObjectData todos los miembros necesarios se serializan, incluidos los miembros de la clase base, si los hay.

Conclusión

Espero que hayas aprendido algo nuevo de este artículo y te hayas convertido en un experto en el ámbito de la serialización. Siguiendo las reglas y siguiendo los consejos que hemos dado anteriormente, ahorrará tiempo al depurar el programa y facilitará su vida y la de otros desarrolladores que trabajan con sus clases. El analizador PVS-Studio también será de gran ayuda, ya que le permitirá detectar dichos errores justo después de que aparezcan en su código.

Información adicional

  • V3094. Posible excepción al deserializar el tipo. Falta el constructor Ctor(SerializationInfo, StreamingContext)
  • V3096. Posible excepción al serializar el tipo. Falta el atributo [Serializable]
  • V3097. Posible excepción:el tipo marcado por [Serializable] contiene miembros no serializables no marcados por [NonSerialized]
  • V3099. No todos los miembros de tipo se serializan dentro del método 'GetObjectData'
  • V3103. No se podrá acceder a un constructor Ctor privado (SerializationInfo, StreamingContext) en tipo sin sellar al deserializar tipos derivados
  • V3104. La implementación de 'GetObjectData' en tipo sin sellar no es virtual, es posible una serialización incorrecta del tipo derivado
  • MSDN. Serialización en .NET Framework
  • MSDN. Serialización personalizada