Serialización personalizada
Puede personalizar el proceso de serialización si implementa la interfaz ISerializable en un objeto. Esto resulta particularmente útil en casos en los que el valor de una variable miembro no es válido tras la deserialización pero es necesario proporcionar un valor a la variable para reconstruir el estado completo del objeto. No debe utilizar la serialización predeterminada en clases que estén marcadas con el atributo Serializable y que tengan seguridad imperativa o declarativa en el nivel de clase o en sus constructores. En lugar de ello, estas clases deben implementar siempre la interfaz ISerializable.
La implementación de ISerializable implica la implementación del método GetObjectData y de un constructor especial que se utiliza al deserializar el objeto. En el siguiente código de ejemplo se muestra cómo implementar ISerializable en la clase MyObject
de una sección anterior.
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;
public MyObject()
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
Cuando se llama a GetObjectData durante la serialización, usted es el responsable de rellenar la clase SerializationInfo proporcionada con la llamada al método. No tiene más que agregar las variables que se van a serializar como parejas de nombre y valor. Se puede utilizar cualquier texto como nombre. Tiene libertad para decidir las variables miembro que se agregan a SerializationInfo, siempre y cuando se serialicen datos suficientes para restaurar el objeto durante la deserialización. Las clases derivadas deben llamar al método GetObjectData en el objeto base si éste implementa ISerializable.
Observe que la serialización puede permitir que otro código vea o modifique los datos de la instancia del objeto, a los que no se podría tener acceso en cualquier otro caso. Por lo tanto, el código encargado de llevar a cabo la serialización requiere un permiso SecurityPermission con el indicador SerializationFormatter especificado. De acuerdo con la directiva predeterminada, no se concede este permiso al código descargado de Internet o de la intranet; únicamente el código del equipo local tiene garantizado este permiso. El método GetObjectData debe protegerse explícitamente, ya sea mediante la petición de SecurityPermission con el indicador SerializationFormatter especificado o mediante la petición de otros permisos que protejan datos privados en particular.
Si un campo privado almacena información confidencial, debe solicitar los permisos adecuados en GetObjectData para proteger los datos. Recuerde que el código que tiene garantizado el permiso SecurityPermission con el indicador SerializationFormatter especificado puede ver y modificar los datos almacenados en campos privados. Un llamador malicioso que tenga garantizado este permiso SecurityPermission puede ver datos como, por ejemplo, ubicaciones de directorios ocultos o permisos garantizados y utilizarlos para atacar puntos de seguridad vulnerables del equipo. Para obtener una lista completa de los indicadores de permisos de seguridad que puede especificar, vea SecurityPermissionFlag (Enumeración).
Es importante resaltar que cuando se agrega ISerializable a una clase, es necesario implementar tanto GetObjectData como el constructor especial. El compilador le avisará si no encuentra GetObjectData. No obstante, dado que no se puede exigir la implementación de un constructor, no se proporcionarán advertencias si no se encuentra y se producirá una excepción al tratar de deserializar una clase sin el constructor.
Se prefirió el diseño actual sobre el método SetObjectData para obtener más seguridad potencial y evitar problemas de control de versiones. Por ejemplo, el método SetObjectData debe ser público si se define como parte de una interfaz; por lo que los usuarios deben escribir código para no tener que llamar varias veces al método SetObjectData. De lo contrario, una aplicación maliciosa que llame al método SetObjectData en un objeto durante el proceso de ejecución de una operación puede producir problemas.
Durante la deserialización, SerializationInfo se pasa a la clase con el constructor proporcionado para este propósito. Las restricciones de visibilidad del constructor se pasan por alto cuando se deserializa el objeto; por lo que la clase puede marcarse como pública, protegida, interna o privada. Sin embargo, es conveniente crear el constructor como protegido a menos que la clase esté sellada, en cuyo caso el constructor debe marcarse como privado. El constructor debe realizar también validación de entrada exhaustiva. Para evitar un uso indebido por parte del código malicioso, el constructor debe aplicar las mismas comprobaciones de seguridad y los mismos permisos que los requeridos para obtener una instancia de la clase utilizando cualquier otro constructor. Si no hace caso de esta recomendación, el código malicioso puede serializar un objeto previamente, obtener control con el permiso SecurityPermission que tiene el indicador SerializationFormatter especificado y deserializar el objeto en un equipo de cliente pasando por alto cualquier tipo de seguridad aplicada durante las construcciones de instancias estándar utilizando un constructor público.
Para restaurar el estado del objeto, no tiene más recuperar los valores de las variables de SerializationInfo con los nombres utilizados durante la serialización. Si la clase básica implementa ISerializable, se debe llamar al constructor básico para permitir que el objeto básico restaure sus variables.
Cuando se deriva una nueva clase de una que implementa ISerializable, la clase derivada debe implementar el constructor y el método GetObjectData si tiene variables que se deban serializar. En el siguiente ejemplo de código se muestra cómo hacer esto con la clase MyObject
mostrada anteriormente.
[Serializable]
public class ObjectTwo : MyObject
{
public int num;
public ObjectTwo() : base()
{
}
protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
{
num = si.GetInt32("num");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
No olvide llamar a la clase base en el constructor de deserialización; si no lo hace, nunca se podrá llamar al constructor de la clase base, y el objeto no se construirá completamente después de la serialización.
Los objetos se reconstruyen desde dentro hacia fuera; y llamar a métodos durante la deserialización puede tener efectos colaterales no deseados porque los métodos llamados pueden hacer referencia a objetos que no se han deserializado en el momento de realizar la llamada. Si la clase que se va deserializar implementa la interfaz IDeserilizationCallback, se llama automáticamente al método OnDeserialization cuando se ha deserializado el gráfico del objeto completo. En este momento, todos los objetos secundarios a los que se hace referencia se han restaurado completamente. Una tabla hash constituye un ejemplo típico de una clase que es difícil deserializar sin utilizar el agente de escucha de eventos descrito anteriormente. Es fácil recuperar las parejas de clave y valor durante la deserialización, pero agregar estos objetos de nuevo a la tabla hash puede causar problemas porque no existe ninguna garantía de que se hayan deserializado las clases derivadas de la tabla hash. Por lo tanto, no es aconsejable llamar a métodos en una tabla hash en esta fase.
Vea también
Serialización binaria | Acceso a objetos de otros dominios de aplicación mediante .NET Remoting | Serialización XML y SOAP | Seguridad y serialización | Seguridad de acceso a código