Condividi tramite


Serializzazione personalizzata

È possibile personalizzare il processo di serializzazione implementando l'interfaccia ISerializable su un oggetto. Questa operazione è particolarmente utile nel caso in cui il valore di una variabile membro non sia valido dopo la deserializzazione, ma è necessario fornire un valore alla variabile per ricostruire lo stato completo dell'oggetto. È inoltre consigliabile non utilizzare la serializzazione predefinita su una classe contrassegnata con l'attributo Serializable che dispone di protezione dichiarativa o imperativa a livello di classe o sui relativi costruttori. Per tali classi è invece opportuno implementare sempre l'interfaccia ISerializable.

L'implementazione di ISerializable implica l'implementazione del metodo GetObjectData e di un costruttore speciale che verrà utilizzato quando l'oggetto viene deserializzato. Il codice di esempio riportato di seguito illustra come implementare ISerializable sulla classe MyObject da una sezione precedente.

[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);
  }
}

Quando GetObjectData viene chiamato durante la serializzazione, è necessario compilare SerializationInfo fornito con la chiamata al metodo. È sufficiente aggiungere le variabili da serializzare come coppie nome/valore. Il nome può essere composto da qualsiasi testo. È possibile scegliere le variabili membro da aggiungere a SerializationInfo, a condizione che venga serializzata una quantità sufficiente di dati per ripristinare l'oggetto durante la deserializzazione. Le classi derivate devono chiamare il metodo GetObjectData sull'oggetto base se quest'ultimo implementa ISerializable.

Si noti che la serializzazione può consentire ad altro codice di visualizzare o modificare i dati di un'istanza di un oggetto che non sarebbero altrimenti accessibili. Con il codice che esegue la serializzazione è pertanto necessario utilizzare la SecurityPermission con il flag SerializationFormatter specificato. Con i criteri predefiniti tale autorizzazione non viene concessa a codice Intranet o scaricato da Internet, ma solo a codice presente sul computer locale. È opportuno proteggere il metodo GetObjectData in modo esplicito richiedendo la SecurityPermission con il flag SerializationFormatter specificato oppure richiedendo altre autorizzazioni per la specifica protezione di dati privati.

Se in un campo privato vengono archiviate informazioni riservate, occorrerà proteggere i dati richiedendo le appropriate autorizzazioni su GetObjectData. Si ricordi che con il codice a cui è stato concesso SecurityPermission con il flag SerializationFormatter specificato è possibile visualizzare e modificare i dati archiviati nei campi privati. Un chiamante malintenzionato a cui sia stata concessa la SecurityPermission può visualizzare dati quali i percorsi delle directory nascoste o le autorizzazioni concesse e utilizzare tali informazioni per individuare un punto debole nella protezione del computer. Per un elenco completo dei flag delle autorizzazioni di protezione che è possibile specificare, vedere l'enumerazione SecurityPermissionFlag.

È importante sottolineare che quando ISerializable viene aggiunto a una classe, sarà necessario implementare sia GetObjectData che il costruttore speciale. In mancanza di GetObjectData, il compilatore emetterà un avviso. Poiché è però impossibile imporre l'implementazione di un costruttore, in mancanza del costruttore l'avviso non verrà emesso, ma verrà generata un'eccezione quando si tenterà di deserializzare una classe priva di costruttore.

La progettazione corrente è stata preferita al metodo SetObjectData per risolvere potenziali problemi legati alla sicurezza e alla compatibilità tra le versioni. Un metodo SetObjectData, ad esempio, deve essere pubblico se viene definito come parte di un'interfaccia. È pertanto necessario scrivere codice che impedisca di chiamare il metodo SetObjectData più volte. In caso contrario, un'applicazione dannosa che dovesse chiamare il metodo SetObjectData su un oggetto durante lo svolgimento di un'operazione potrebbe causare problemi.

Durante la deserializzazione, SerializationInfo viene passato alla classe utilizzando il costruttore fornito per questo specifico scopo. Quando l'oggetto viene deserializzato, qualsiasi vincolo di visibilità apposto al costruttore verrà ignorato. È pertanto possibile contrassegnare la classe come pubblica, protetta, interna o privata. È comunque buona norma rendere il costruttore protetto, a meno che la classe non sia sealed, nel qual caso il costruttore dovrà essere contrassegnato come privato. Nel costruttore andrebbe anche svolta una completa convalida dell'input. Per evitare abusi di codice dannoso, è bene che il costruttore imponga gli stessi controlli di protezione e le stesse autorizzazioni richieste per ottenere un'istanza della classe utilizzando qualsiasi altro costruttore. Se non si osservano queste raccomandazioni, codice dannoso potrebbe preserializzare un oggetto, ottenere il controllo con il SecurityPermission con il flag SerializationFormatter specificato e deserializzare l'oggetto su un computer client aggirando le protezioni che sarebbero state applicate durante una costruzione di istanza standard svolta mediate un costruttore pubblico.

Per ripristinare lo stato dell'oggetto, è sufficiente recuperare i valori delle variabili da SerializationInfo mediante i nomi utilizzati durante la serializzazione. Se la classe base implementa ISerializable, deve essere chiamato il costruttore base per consentire all'oggetto base di ripristinare le proprie variabili.

Quando una nuova classe viene derivata da una classe che implementa ISerializable, la nuova classe dovrà implementare sia il costruttore che il metodo GetObjectData, se contiene variabili da serializzare. Nell'esempio di codice riportato di seguito vengono illustrate le modalità per eseguire questa operazione utilizzando la classe MyObject descritta in precedenza.

[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);
  }
}

Ricordarsi di chiamare la classe base nel costruttore di deserializzazione. Se questa operazione non viene eseguita, il costruttore nella classe base non verrà mai chiamato e l'oggetto non sarà costruito completamente dopo la deserializzazione.

Gli oggetti vengono ricostruiti dall'interno verso l'esterno e la chiamata di metodi durante la deserializzazione può provocare effetti indesiderati, in quanto i metodi chiamati possono fare riferimento a riferimenti a oggetti che non sono stati ancora deserializzati al momento della chiamata. Se la classe deserializzata implementa IDeserilizationCallback, il metodo OnDeserialization verrà chiamato automaticamente dopo la deserializzazione dell'intero oggetto grafico. A questo punto tutti gli oggetti figlio a cui si è fatto riferimento sono stati completamente ripristinati. Una tabella hash è un tipico esempio di una classe difficile da deserializzare senza l'utilizzo del listener di eventi descritto sopra. Sebbene sia facile recuperare le coppie chiave/valore durante la deserializzazione non è altrettanto facile aggiungere nuovamente gli oggetti alla tabella hash perché non esiste alcuna garanzia che le classi derivate dalla tabella hash siano state deserializzate. Non è quindi consigliabile chiamare i metodi su una tabella hash in questa fase.

Vedere anche

Serializzazione binaria | Accesso a oggetti in altri domini applicazione mediante .NET Remoting | Serializzazione XML e SOAP | Protezione e serializzazione | Protezione dall'accesso di codice