Warum folgt der ContentManager von XNA generischen Typparametern für Serialisierungszwecke?

8

Ich bin endlich einem Problem auf den Grund gegangen und frage mich, was mein bester Rückgriff ist. Kurz gesagt, das Problem besteht darin, dass XNAs ReflectiveReaderin generische Typparameter reflektiert werden, selbst wenn keine Instanz dieses generischen Typs in dem zu serialisierenden Objekt gespeichert ist.

Ein Beispiel zeigt dies am besten. Betrachten Sie die folgenden Modellklassen:

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

Angenommen, ich möchte eine Instanz von LevelData in einer XML-Datei definieren, die später mit ContentManager( Test.xml ) geladen werden soll :

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

Betrachten Sie nun diese einfache Ladelogik:

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

Die erste Zeile ist erfolgreich, die zweite löst jedoch eine Ausnahme aus:

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: 

Wenn ich in der Zeile, die die Textur lädt, einen Haltepunkt setze und dann das Element untersuche ContentTypeReaderManager.nameToReader, sehe ich Folgendes:

Geben Sie hier die Bildbeschreibung ein

Wie Sie sehen können, ReflectiveReaderwird tatsächlich a für den Texture2DTyp zugeordnet. Dies stammt aus meiner TestEntityKlasse (siehe die Einträge über dem im obigen Bild hervorgehobenen). Aber wenn Sie meine Modellklassen untersuchen, hat nichts, was hängt, LevelDataeine Instanz von TestEntityoder sogar Entitydarin!

Wenn ich die TestEntityDataKlasse in diese ändere :

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

Die Ausnahme tritt nicht mehr auf. Das liegt daran, dass TestEntityes nie in Betracht gezogen wird, also auch nicht Texture2D. Daher werden ReflectiveReaderdie generischen Typparameter in meinen Modellklassen betrachtet und verfolgt! Ich kann nur annehmen, dass dies ein Fehler ist - es macht für mich überhaupt keinen Sinn, warum dies notwendig wäre.

Meine Modellklassen haben diese generischen Typparameter aus gutem Grund - sie machen meinen Modellcode viel einfacher. Bin ich hier fest? Ist meine einzige Option, meine Modelle so umzugestalten, dass sie niemals einen generischen Typparameter meiner Entitätstypen haben? Ich habe überlegt, zu verwenden ContentSerializerIgnoreAttribute, aber das funktioniert nur gegen Eigenschaften und Felder, was sinnvoll ist, wenn man bedenkt, dass dies die einzigen Dinge sind, die die Serialisierung beeinflussen sollten.

Hat jemand einen Rat?

mir--
quelle
Ich bin mit XNA nicht vertraut, aber wenn Sie Texture2D aus der Überlegung entfernen, wie kann es Load<Texture2D>gelingen, ohne eine Ausnahme auszulösen ? Ihre Frage ist ziemlich klar, aber es ist nicht klar, wie sich Ihr Beispiel darauf bezieht. Ich würde jedoch sagen, dass bei der Serialisierung generische Typen berücksichtigt werden müssen, da sonst nicht garantiert werden kann, dass alles, was aus dem Stream gelesen wird, rekonstruiert werden kann.
Kylotan
Das Aufrufen Load<Texture2D>funktioniert, wenn der reflektierende Leser nicht zuerst dort angekommen ist und behauptet, dass er für das Laden von Texturen verantwortlich ist. Wenn ich zum Beispiel den Aufruf zum Laden meiner Teststufe überspringe, wird die Textur erfolgreich mit XNAs TextureReaderoder wie auch immer sie heißt geladen . Ich bestreite, dass die generischen Parameter einen Einfluss auf die Serialisierung haben. Die Serialisierung befasst sich nur mit dem Status eines Objekts, und das betreffende Objekt enthält keine Entität. Der generische Parameter wird nur in Methoden für das Objekt verwendet, nicht in Daten.
Ich
@ user13414, die Serialisierung muss genau wissen, um welche Art von Objekt es sich handelt, um es am anderen Ende neu zu erstellen. Es müssen beispielsweise Konstruktoren aufgerufen werden. Und der Typ des Objekts enthält das spezifische Argument, das als generischer Parameter übergeben wird, zumindest in Sprachen wie C # und C ++ (möglicherweise nicht in Java, das Generika etwas anders implementiert).
Kylotan
@Kylotan: Die Basisklasse ist generisch, nicht die Unterklasse (das zu serialisierende Objekt). Es ist ein geschlossener generischer Typ, kein offener.
Ich
2
In den von mir verlinkten Dokumenten wird angegeben, dass in .NET Reflection Informationen zu den generischen Typen bezüglich ihrer Typparameter gespeichert sind. Diese Informationen können über Type.GetGenericArgumentseinen geschlossenen generischen Typ oder einen offenen generischen Typ abgerufen werden . Möglicherweise sind die Dokumente falsch und Sie haben Recht, aber die Dokumente erklären, warum Texture2D vom Reflection-System abgedeckt wird und daher in Ihrem Serialisierungscode angezeigt wird. Vielleicht könnten Sie bei MSDN nachfragen, da anscheinend hier niemand eine bessere Idee hat.
Kylotan

Antworten:

4

Zwar muss sich die Serialisierung im Allgemeinen nicht unbedingt mit den Typen der betreffenden Objekte befassen und nur Darstellungen ihres Zustands aufzeichnen ... dies tun nicht alle Serialisierungsimplementierungen. Die meisten der integrierten .NET Serialisierungsmethoden tun Rekord Informationen über Arten in Serialisierung teilnehmen. Diese Auswahl hat sowohl Vorteile (was eine robustere Validierung ermöglicht) als auch Nachteile (größere serialisierte Objektgröße), aber es ist an sich nicht falsch und Sie müssen nur damit leben.

Die XNA-Inhaltspipeline für Ihre Typen durchläuft das serialisierbare Eigenschafts- (und Feld-) Diagramm und erstellt Lesegeräte für diese. Sie können dieses Verhalten sehen, wenn Sie die Initialisierung auf ReflectiveReader<T>(die InitializeMethode, nicht den Konstruktor) untersuchen. Dies geschieht über Reflexion, nicht basierend auf den tatsächlichen Daten im XML (dies kann wiederum anhand des reflektierten Codes überprüft werden). Es spielt also keine Rolle, ob Ihre Daten einen Verweis auf die Textur enthalten oder nicht. Wenn das Texture2DEigenschaftsdiagramm des Typs eine Eigenschaft enthält, wird versucht, im Rahmen der Initialisierung der Inhaltspipeline einen Reader dafür zu erstellen.

Sie sollten keine direkten Verweise auf Texture2DObjekte in Ihrem benutzerdefinierten Inhalt verwenden. Möglicherweise finden Sie diesen Thread (oder diesen in geringerem Maße). Die angebliche Lösung des Problems besteht darin, Texture2DContentstattdessen externe Verweise auf zu verwenden.


quelle