Finalmente cheguei ao fundo de um problema e estou me perguntando qual é o meu melhor recurso. Em resumo, o problema é que os XNAs ReflectiveReader
refletem 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.nameToReader
membro, vejo o seguinte:
Como você pode ver, a ReflectiveReader
está realmente sendo mapeada para o Texture2D
tipo. Isso deriva da minha TestEntity
turma (veja as entradas acima da destacada na imagem acima). Mas se você examinar minhas classes de modelo, nada de pendurado LevelData
tem uma instância TestEntity
ou mesmo Entity
nela!
Se eu mudar a TestEntityData
classe para isso:
public sealed class TestEntityData : EntityData<TestEntityData, Entity>
{
}
A exceção não ocorre mais. Isso porque TestEntity
nunca é considerado, nem o é Texture2D
. Portanto, ReflectiveReader
está 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?
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 TextureReader
ou 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.
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.
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.