Je suis enfin allé au fond d'un problème et je me demande quel est mon meilleur recours. En bref, le problème est que les XNA se ReflectiveReader
reflètent dans les paramètres de type générique, même si aucune instance de ce type générique n'est stockée dans l'objet en cours de sérialisation.
Un exemple le démontre le mieux. Considérez les classes de modèle suivantes:
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;
}
}
}
Supposons maintenant que je souhaite définir une instance de LevelData dans un fichier XML à charger ultérieurement avec 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>
Considérez maintenant cette logique de chargement simple:
Content.Load<LevelData>("Test");
Content.Load<Texture2D>("Texture");
La première ligne réussit, mais la seconde lève une exception:
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:
Si je mets un point d'arrêt sur la ligne qui charge la texture puis examine le ContentTypeReaderManager.nameToReader
membre, je vois ceci:
Comme vous pouvez le voir, a ReflectiveReader
est en effet mappé pour le Texture2D
type. Cela vient de ma TestEntity
classe (voir les entrées au-dessus de celle mise en évidence dans l'image ci-dessus). Mais si vous examinez mes classes de modèles, rien de suspendu LevelData
n'a une instance TestEntity
ou même Entity
dedans!
Si je change la TestEntityData
classe en ceci:
public sealed class TestEntityData : EntityData<TestEntityData, Entity>
{
}
L'exception ne se produit plus. C'est parce que TestEntity
n'est jamais considéré, donc non plus Texture2D
. Ainsi, le ReflectiveReader
regarde - et suit - les paramètres de type génériques dans mes classes de modèles! Je peux seulement supposer que c'est un bug - cela n'a aucun sens pour moi pourquoi cela serait nécessaire.
Mes classes de modèles ont ces paramètres de type génériques pour une bonne raison - elles rendent mon code de modèle beaucoup plus simple. Suis-je coincé ici? Est-ce que ma seule option est de refactoriser mes modèles pour ne jamais avoir de paramètre de type générique de mes types d'entité? J'ai envisagé d'utiliser ContentSerializerIgnoreAttribute
, mais cela ne fonctionne que contre les propriétés et les champs, ce qui est logique étant donné que ce sont les seules choses qui devraient influencer la sérialisation.
Quelqu'un a-t-il des conseils?
Load<Texture2D>
réussir sans déclencher une exception? Votre question est assez claire mais il n'est pas clair comment votre exemple s'y rapporte. Je dirais cependant que la sérialisation doit se pencher sur les types génériques, car sinon, il ne peut pas être garanti de pouvoir reconstruire tout ce qu'elle lit dans le flux.Load<Texture2D>
fonctionne si le lecteur réfléchissant n'y est pas entré en premier et prétend qu'il est responsable du chargement des textures. Si, par exemple, je saute l'appel pour charger mon niveau de test, la texture se charge avec succès à l'aide de XNATextureReader
ou de tout autre nom. Je conteste que les paramètres génériques aient une incidence sur la sérialisation. La sérialisation ne concerne que l'état d'un objet, et l'objet en question n'a pas d'entité en lui. Le paramètre générique n'est utilisé que dans les méthodes sur l'objet, pas dans les données.Type.GetGenericArguments
, qu'il s'agisse d'un type générique fermé ou d'un type générique ouvert. Peut-être que les documents sont faux et que vous avez raison, mais les documents expliquent pourquoi Texture2D est couvert par le système Reflection et apparaît donc dans votre code de sérialisation. Peut-être pourriez-vous demander sur MSDN car il ne semble pas que quiconque ici ait une meilleure idée.Réponses:
Bien qu'il soit vrai qu'en général , la sérialisation ne doit pas nécessairement se préoccuper des types des objets en question et n'enregistrer que des représentations de leur état ... toutes les implémentations de sérialisation ne le font pas. La plupart des méthodes de sérialisation .NET intégré dans le faire enregistrer des informations sur les types qui participent à la sérialisation. Il y a des avantages à ce choix (permettant une validation plus robuste) ainsi que des inconvénients (taille d'objet sérialisé plus grande), mais ce n'est pas faux en soi et il suffit de vivre avec.
Le pipeline de contenu de XNA, pour vos types, traverse le graphique de propriété (et de champ) sérialisable et crée des lecteurs pour eux. Vous pouvez voir ce comportement si vous examinez l'initialisation de
ReflectiveReader<T>
(laInitialize
méthode, pas le constructeur). Il le fait via la réflexion, pas sur la base des données réelles dans le XML (encore une fois, cela est vérifiable en regardant le code réfléchi). Peu importe qu'il y ait une référence à la texture dans vos données ou non, s'il y a uneTexture2D
propriété dans le graphique des propriétés du type, il essaiera de créer un lecteur pour cela dans le cadre de l'initialisation du pipeline de contenu.Vous n'êtes pas censé utiliser des références directes aux
Texture2D
objets dans votre contenu personnalisé. Vous pouvez trouver ce fil (ou celui-ci , dans une moindre mesure). La solution alléguée au problème consiste à utiliser des références externes à laTexture2DContent
place.la source