¿Por qué el ContentManager de XNA sigue parámetros de tipo genérico para fines de serialización?

8

Finalmente he llegado al fondo de un problema y me pregunto cuál es mi mejor recurso. En resumen, el problema es que los XNA se ReflectiveReaderreflejan en parámetros de tipo genérico, incluso si no se almacena ninguna instancia de ese tipo genérico en el objeto que se está serializando.

Un ejemplo lo demuestra mejor. Considere las siguientes clases de modelo:

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

Ahora suponga que quiero definir una instancia de LevelData dentro de un archivo XML para luego cargarla 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>

Ahora considere esta lógica de carga simple:

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

La primera línea tiene éxito, pero la segunda arroja una excepción:

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 establezco un punto de interrupción en la línea que carga la textura y luego examino el ContentTypeReaderManager.nameToReadermiembro, veo esto:

ingrese la descripción de la imagen aquí

Como puede ver, ReflectiveReaderse está mapeando a para el Texture2Dtipo. Esto se deriva de mi TestEntityclase (ver las entradas arriba de la destacada en la imagen de arriba). Pero si examinas mis clases de modelos, ¡nada que cuelgue LevelDatatiene una instancia TestEntityo incluso Entityen ella!

Si cambio la TestEntityDataclase a esto:

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

La excepción ya no ocurre. Eso es porque TestEntitynunca se considera, por lo que tampoco lo es Texture2D. ¡Por lo tanto, ReflectiveReaderestá mirando, y siguiendo, los parámetros de tipo genérico en mis clases de modelo! Solo puedo suponer que esto es un error, no tiene ningún sentido para mí por qué esto sería necesario.

Mis clases de modelo tienen estos parámetros de tipo genérico por una buena razón: hacen que el código de mi modelo sea mucho más simple. ¿Estoy atrapado aquí? ¿Es mi única opción refactorizar mis modelos para que nunca tengan un parámetro de tipo genérico de mis tipos de entidad? Pensé en usar ContentSerializerIgnoreAttribute, pero eso solo funciona contra propiedades y campos, lo que tiene sentido teniendo en cuenta que son las únicas cosas que deberían influir en la serialización.

Alguien tiene algún consejo?

yo--
fuente
No estoy familiarizado con XNA, pero si elimina Texture2D de la consideración, ¿cómo puede Load<Texture2D>tener éxito sin generar una excepción? Su pregunta es bastante clara, pero no está claro cómo se relaciona su ejemplo con ella. Sin embargo, diría que la serialización tiene que buscar tipos genéricos, porque de lo contrario no se puede garantizar que pueda reconstruir lo que lea de la transmisión.
Kylotan
Las llamadas Load<Texture2D>funcionan si el lector reflexivo no ha entrado allí primero y afirma que es responsable de cargar texturas. Si, por ejemplo, me salto la llamada para cargar mi nivel de prueba, la textura se carga con éxito usando XNA TextureReadero como se llame. Discuto que los parámetros genéricos tengan alguna relación con la serialización. La serialización se refiere solo al estado de un objeto, y el objeto en cuestión no tiene entidad. El parámetro genérico solo se usa en métodos en el objeto, no en datos.
yo--
@ user13414, la serialización necesita saber exactamente qué tipo de objeto es para recrearlo en el otro extremo; habrá constructores a los que llamar, por ejemplo. Y el tipo de objeto incluye el argumento específico pasado como un parámetro genérico, al menos en lenguajes como C # y C ++ (tal vez no en Java, que implementa los genéricos de manera algo diferente).
Kylotan
@Kylotan: la clase base es genérica, no la subclase (que es el objeto que se está serializando). Es un tipo genérico cerrado, no abierto.
yo--
2
Los documentos que vinculé para indicar que la reflexión .NET almacena información sobre los tipos genéricos con respecto a sus parámetros de tipo, y esto se puede obtener a través de Type.GetGenericArguments, ya sea un tipo genérico cerrado o un tipo genérico abierto. Tal vez los documentos están equivocados y usted tiene razón, pero los documentos explican por qué Texture2D está cubierto por el sistema Reflection y, por lo tanto, aparecen en su código de serialización. Tal vez podría preguntar en MSDN ya que no parece que nadie aquí tenga una mejor idea.
Kylotan

Respuestas:

4

Si bien es cierto que, en general , la serialización no necesariamente tiene que preocuparse por los tipos de los objetos en cuestión y solo registrar las representaciones de su estado ... no todas las implementaciones de serialización hacen eso. La mayoría de los métodos de serialización incorporado en .NET hacer registran información sobre los tipos de participantes en la serialización. Hay ventajas en esa elección (que permite una validación más sólida), así como desventajas (mayor tamaño de objeto serializado), pero no está mal per se y solo tienes que vivir con él.

La canalización de contenido de XNA, para sus tipos, atraviesa el gráfico de propiedades serializables (y de campo) y crea lectores para ellos. Puede ver este comportamiento si examina la inicialización de ReflectiveReader<T>(el Initializemétodo, no el constructor). Lo hace a través de la reflexión, no se basa en los datos reales en el XML (de nuevo, esto se puede verificar observando el código reflejado). Por lo tanto, no importa si hay una referencia a la textura en sus datos o no, si hay una Texture2Dpropiedad en el gráfico de propiedades del tipo, intentará crear un lector para ello como parte de la inicialización de la canalización de contenido.

Se supone que no debe utilizar referencias directas a Texture2Dobjetos en su contenido personalizado. Puede encontrar este hilo (o este , en menor grado). La supuesta solución al problema es utilizar referencias externas en su Texture2DContentlugar.


fuente