Pourquoi ContentManager de XNA suit-il les paramètres de type génériques à des fins de sérialisation?


8

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 ReflectiveReaderreflè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.nameToReadermembre, je vois ceci:

entrez la description de l'image ici

Comme vous pouvez le voir, a ReflectiveReaderest en effet mappé pour le Texture2Dtype. Cela vient de ma TestEntityclasse (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 LevelDatan'a une instance TestEntityou même Entitydedans!

Si je change la TestEntityDataclasse en ceci:

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

L'exception ne se produit plus. C'est parce que TestEntityn'est jamais considéré, donc non plus Texture2D. Ainsi, le ReflectiveReaderregarde - 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?


Je ne connais pas XNA, mais si vous retirez Texture2D de la considération, comment peut-il 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.
Kylotan

L'appel 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 XNA TextureReaderou 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.
moi--

@ user13414, la sérialisation doit savoir exactement de quel type d'objet il s'agit afin de le recréer à l'autre bout - il y aura des constructeurs à appeler, par exemple. Et le type de l'objet inclut l'argument spécifique passé en tant que paramètre générique, au moins dans des langages comme C # et C ++ (peut-être pas en Java, qui implémente les génériques quelque peu différemment).
Kylotan

@Kylotan: la classe de base est générique, pas la sous-classe (qui est l'objet en cours de sérialisation). Il s'agit d'un type générique fermé et non ouvert.
moi

2
Les documents que j'ai liés indiquent que la réflexion .NET stocke des informations sur les types génériques concernant leurs paramètres de type, et cela peut être obtenu via 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.
Kylotan

Réponses:


4

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>(la Initializemé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 une Texture2Dproprié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 Texture2Dobjets 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 à la Texture2DContentplace.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.