Por que o ContentManager da XNA segue parâmetros de tipo genérico para fins de serialização?


8

Finalmente cheguei ao fundo de um problema e estou me perguntando qual é o meu melhor recurso. Em resumo, o problema é que os XNAs ReflectiveReaderrefletem em parâmetros de tipo genérico, mesmo se nenhuma instância desse tipo genérico estiver armazenada no objeto que está sendo serializado.

Um exemplo melhor demonstra isso. Considere as seguintes classes 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;
        }
    }
}

Agora, suponha que eu queira definir uma instância de LevelData dentro de um arquivo XML para ser carregada posteriormente com o 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>

Agora considere esta lógica de carregamento simples:

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

A primeira linha é bem-sucedida, mas a segunda gera uma exceção:

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 eu definir um ponto de interrupção na linha que carrega a textura e examinar o ContentTypeReaderManager.nameToReadermembro, vejo o seguinte:

insira a descrição da imagem aqui

Como você pode ver, a ReflectiveReaderestá realmente sendo mapeada para o Texture2Dtipo. Isso deriva da minha TestEntityturma (veja as entradas acima da destacada na imagem acima). Mas se você examinar minhas classes de modelo, nada de pendurado LevelDatatem uma instância TestEntityou mesmo Entitynela!

Se eu mudar a TestEntityDataclasse para isso:

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

A exceção não ocorre mais. Isso porque TestEntitynunca é considerado, nem o é Texture2D. Portanto, ReflectiveReaderestá olhando - e seguindo - os parâmetros de tipo genérico nas minhas classes de modelo! Só posso assumir que isso é um bug - não faz sentido para mim porque isso seria necessário.

Minhas classes de modelo possuem esses parâmetros de tipo genéricos por um bom motivo - elas tornam meu código de modelo muito mais simples. Eu estou preso aqui? Minha única opção é refatorar meus modelos para nunca ter um parâmetro de tipo genérico dos meus tipos de entidade? Eu considerei usar ContentSerializerIgnoreAttribute, mas isso só funciona com propriedades e campos, o que faz sentido, considerando que são as únicas coisas que devem influenciar a serialização.

Alguém tem algum conselho?


Não estou familiarizado com o XNA, mas se você remover o Texture2D de consideração, como pode Load<Texture2D>ter sucesso sem gerar uma exceção? Sua pergunta é bastante clara, mas não está claro como o seu exemplo está relacionado a ela. Eu diria, no entanto, que a serialização precisa examinar tipos genéricos, porque, caso contrário, não é possível garantir a reconstrução do que for lido do fluxo.
Kylotan

Ligar Load<Texture2D>funciona se o leitor reflexivo não chegar lá primeiro e alegar que é responsável por carregar as texturas. Se, por exemplo, pular a chamada para carregar meu nível de teste, a textura será carregada com sucesso usando XNAs TextureReaderou o que for chamado. Eu discuto que os parâmetros genéricos têm alguma influência na serialização. A serialização se preocupa apenas com o estado de um objeto, e o objeto em questão não possui entidade. O parâmetro genérico é usado apenas em métodos no objeto, não em dados.
me--

@ user13414, a serialização precisa saber exatamente que tipo de objeto é para recriá-lo na outra extremidade - haverá construtores para chamar, por exemplo. E o tipo do objeto inclui o argumento específico passado como parâmetro genérico, pelo menos em linguagens como C # e C ++ (talvez não em Java, que implementa genéricos de maneira um pouco diferente).
Kylotan

@Kylotan: a classe base é genérica, não a subclasse (que é o objeto que está sendo serializado). É um tipo genérico fechado, não aberto.
me--

2
Os documentos aos quais vinculei afirmam que a reflexão do .NET armazena informações sobre os tipos genéricos em relação aos seus parâmetros de tipo, e isso pode ser obtido via Type.GetGenericArguments, seja um tipo genérico fechado ou um tipo genérico aberto. Talvez os documentos estejam errados e você esteja certo, mas os documentos explicam por que o Texture2D é coberto pelo sistema Reflection e, portanto, aparece no seu código de serialização. Talvez você possa perguntar no MSDN, pois parece que ninguém aqui tem uma ideia melhor.
Kylotan

Respostas:


4

Embora seja verdade que, em geral , a serialização não precisa necessariamente se preocupar com os tipos de objetos em questão e apenas registrar representações de seu estado ... nem todas as implementações de serialização fazem isso. A maioria dos built-in .NET métodos de serialização fazer registrar informações sobre os tipos de participantes na serialização. Há vantagens nessa escolha (permitindo uma validação mais robusta), bem como desvantagens (tamanho de objeto serializado maior), mas não é errado por si só e você só precisa conviver com ele.

O pipeline de conteúdo do XNA, para seus tipos, percorre o gráfico de propriedades serializáveis ​​(e campos) e cria leitores para eles. Você pode ver esse comportamento se examinar a inicialização para ReflectiveReader<T>(o Initializemétodo, não o construtor). Isso é feito através da reflexão, não com base nos dados reais no XML (novamente, isso é verificável observando o código refletido). Portanto, não importa se há ou não uma referência à textura nos seus dados, se houver uma Texture2Dpropriedade no gráfico de propriedades do tipo, ele tentará criar um leitor para ela como parte da inicialização do pipeline de conteúdo.

Você não deve usar referências diretas a Texture2Dobjetos em seu conteúdo personalizado. Você pode encontrar este tópico (ou este , em menor grau). A suposta solução para o problema é usar referências externas para Texture2DContent.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.