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 ReflectiveReader
riflettono 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.nameToReader
membro, vedo questo:
Come puoi vedere, a ReflectiveReader
viene effettivamente mappato per il Texture2D
tipo. Questo deriva dalla mia TestEntity
classe (vedi le voci sopra quella evidenziata nell'immagine sopra). Ma se esamini le mie classi di modello, nulla che pende LevelData
ha un'istanza TestEntity
o addirittura Entity
dentro!
Se cambio la TestEntityData
classe in questo:
public sealed class TestEntityData : EntityData<TestEntityData, Entity>
{
}
L'eccezione non si verifica più. Questo perché TestEntity
non è mai considerato, quindi nemmeno lo è Texture2D
. Quindi, ReflectiveReader
sta 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?
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 TextureReader
o 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.
Type.GetGenericArguments
un 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.
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.