Perché ContentManager di XNA segue parametri di tipo generico ai fini della serializzazione?


8

Sono finalmente arrivato al fondo di un problema e mi chiedo quale sia la mia migliore risorsa. In breve, il problema è che gli XNA si ReflectiveReaderriflettono nei parametri di tipo generico, anche se nessuna istanza di quel tipo generico è memorizzata nell'oggetto serializzato.

Un esempio lo dimostra meglio. Considera le seguenti classi di modelli:

namespace Model
{
    using System.Collections.Generic;
    using Microsoft.Xna.Framework.Graphics;

    public abstract class Entity
    {
    }

    public sealed class TestEntity : Entity
    {
        public Texture2D Texture
        {
            get;
            set;
        }
    }

    public abstract class EntityData
    {
    }

    public abstract class EntityData<TData, TEntity> : EntityData
        where TData : EntityData
        where TEntity : Entity
    {
    }

    public sealed class TestEntityData : EntityData<TestEntityData, TestEntity>
    {
    }

    public sealed class LevelData
    {
        public List<EntityData> Entities
        {
            get;
            set;
        }
    }
}

Supponiamo ora di voler definire un'istanza di LevelData all'interno di un file XML da caricare successivamente con ContentManager( Test.xml ):

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Model="Model">
  <Asset Type="Model:LevelData">
    <Entities>
      <Item Type="Model:TestEntityData">
      </Item>
    </Entities>
  </Asset>
</XnaContent>

Consideriamo ora questa semplice logica di caricamento:

Content.Load<LevelData>("Test");
Content.Load<Texture2D>("Texture");

La prima riga ha esito positivo, ma la seconda genera un'eccezione:

Microsoft.Xna.Framework.Content.ContentLoadException was unhandled
  Message=Error loading "Texture". ContentTypeReader Microsoft.Xna.Framework.Content.Texture2DReader, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553 conflicts with existing handler Microsoft.Xna.Framework.Content.ReflectiveReader`1[[Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553]], Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553 for type Microsoft.Xna.Framework.Graphics.Texture2D.
  Source=Microsoft.Xna.Framework
  StackTrace:
       at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.AddTypeReader(String readerTypeName, ContentReader contentReader, ContentTypeReader reader)
       at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.GetTypeReader(String readerTypeName, ContentReader contentReader, List`1& newTypeReaders)
       at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.ReadTypeManifest(Int32 typeCount, ContentReader contentReader)
       at Microsoft.Xna.Framework.Content.ContentReader.ReadHeader()
       at Microsoft.Xna.Framework.Content.ContentReader.ReadAsset[T]()
       at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[T](String assetName, Action`1 recordDisposableObject)
       at Microsoft.Xna.Framework.Content.ContentManager.Load[T](String assetName)
       at XnaContentManagerRepro.Game1.LoadContent() in D:\Temp\XnaContentManagerRepro\XnaContentManagerRepro\XnaContentManagerRepro\Game1.cs:line 53
       at Microsoft.Xna.Framework.Game.Initialize()
       at XnaContentManagerRepro.Game1.Initialize() in D:\Temp\XnaContentManagerRepro\XnaContentManagerRepro\XnaContentManagerRepro\Game1.cs:line 39
       at Microsoft.Xna.Framework.Game.RunGame(Boolean useBlockingRun)
       at Microsoft.Xna.Framework.Game.Run()
       at XnaContentManagerRepro.Program.Main(String[] args) in D:\Temp\XnaContentManagerRepro\XnaContentManagerRepro\XnaContentManagerRepro\Program.cs:line 15
  InnerException: 

Se imposto un punto di interruzione sulla linea che carica la trama e quindi esamino il ContentTypeReaderManager.nameToReadermembro, vedo questo:

inserisci qui la descrizione dell'immagine

Come puoi vedere, a ReflectiveReaderviene effettivamente mappato per il Texture2Dtipo. Questo deriva dalla mia TestEntityclasse (vedi le voci sopra quella evidenziata nell'immagine sopra). Ma se esamini le mie classi di modello, nulla che pende LevelDataha un'istanza TestEntityo addirittura Entitydentro!

Se cambio la TestEntityDataclasse in questo:

public sealed class TestEntityData : EntityData<TestEntityData, Entity>
{
}

L'eccezione non si verifica più. Questo perché TestEntitynon è mai considerato, quindi nemmeno lo è Texture2D. Quindi, ReflectiveReadersta guardando - e seguendo - i parametri di tipo generico nelle mie classi di modello! Posso solo supporre che si tratti di un bug: per me non ha alcun senso il motivo per cui sarebbe necessario.

Le mie classi di modelli hanno questi parametri di tipo generico per una buona ragione: rendono il mio codice di modello molto più semplice. Sono bloccato qui? La mia unica opzione per refactoring i miei modelli non ha mai un parametro di tipo generico dei miei tipi di entità? Ho considerato l'utilizzo ContentSerializerIgnoreAttribute, ma funziona solo contro proprietà e campi, il che ha senso considerando che sono le uniche cose che dovrebbero influenzare la serializzazione.

Qualcuno ha qualche consiglio?


Non ho familiarità con XNA, ma se rimuovi Texture2D dalla considerazione, come puoi Load<Texture2D>riuscire senza sollevare un'eccezione? La tua domanda è abbastanza chiara ma non è chiaro in che modo il tuo esempio si collega ad esso. Direi tuttavia che la serializzazione deve esaminare i tipi generici, perché altrimenti non si può garantire che sia in grado di ricostruire tutto ciò che legge dallo stream.
Kylotan,

La chiamata Load<Texture2D>funziona se il lettore riflettente non è entrato per primo e ha affermato che è responsabile del caricamento delle trame. Se, ad esempio, salto la chiamata per caricare il mio livello di test, la trama si carica correttamente usando XNA TextureReadero come si chiama. Dubito che i parametri generici abbiano qualche influenza sulla serializzazione. La serializzazione riguarda solo lo stato di un oggetto e l'oggetto in questione non ha entità al suo interno. Il parametro generico viene utilizzato solo nei metodi sull'oggetto, non nei dati.
io--

@ user13414, la serializzazione deve sapere esattamente che tipo di oggetto è per ricrearlo dall'altra parte - ci saranno costruttori da chiamare, per esempio. E il tipo di oggetto include l'argomento specifico passato come parametro generico, almeno in linguaggi come C # e C ++ (forse non in Java, che implementa i generici in modo leggermente diverso).
Kylotan,

@Kylotan: la classe base è generica, non la sottoclasse (che è l'oggetto da serializzare). È un tipo generico chiuso, non aperto.
io--

2
I documenti che ho collegato affermano che .NET reflection memorizza informazioni sui tipi generici riguardanti i loro parametri di tipo, e questo può essere ottenuto tramite Type.GetGenericArgumentsun tipo generico chiuso o un tipo generico aperto. Forse i documenti sono sbagliati e tu hai ragione, ma i documenti spiegano perché Texture2D è coperto dal sistema Reflection e quindi mostrato nel tuo codice di serializzazione. Forse potresti chiedere su MSDN poiché non sembra che qui qualcuno abbia un'idea migliore.
Kylotan,

Risposte:


4

Mentre è vero che in generale , la serializzazione non deve necessariamente riguardare i tipi di oggetti in questione e registra solo le rappresentazioni del loro stato ... non tutte le implementazioni di serializzazione lo fanno. La maggior parte dei built-in .NET metodi di serializzazione fanno registrare informazioni sui tipi che partecipano a serializzazione. Ci sono vantaggi in quella scelta (che consente una validazione più solida) così come svantaggi (dimensioni di oggetti serializzate più grandi), ma di per sé non è sbagliato e devi solo convivere con esso.

La pipeline di contenuti di XNA, per i tuoi tipi, attraversa il grafico delle proprietà (e dei campi) serializzabili e crea i lettori per essi. È possibile visualizzare questo comportamento se si esamina l'inizializzazione di ReflectiveReader<T>(il Initializemetodo, non il costruttore). Lo fa tramite la riflessione, non in base ai dati effettivi nell'XML (di nuovo, ciò è verificabile guardando il codice riflesso). Quindi non importa se c'è un riferimento alla trama nei tuoi dati o meno, se c'è una Texture2Dproprietà nel grafico delle proprietà del tipo, proverà a creare un lettore per esso come parte dell'inizializzazione della pipeline di contenuti.

Non è necessario utilizzare riferimenti diretti agli Texture2Doggetti nel contenuto personalizzato. Puoi trovare questo thread (o questo , in misura minore). La presunta soluzione al problema è invece utilizzare riferimenti esterni Texture2DContent.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.