I've finally gotten to the bottom of a problem and am wondering what my best recourse is. In short, the problem is that XNA's ReflectiveReader
reflects into generic type parameters, even if no instance of that generic type is stored in the object being serialized.
An example best demonstrates this. Consider the following model classes:
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;
}
}
}
Now suppose I want to define an instance of LevelData inside an XML file to be later loaded with the 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>
Now consider this simple load logic:
Content.Load<LevelData>("Test");
Content.Load<Texture2D>("Texture");
The first line succeeds, but the second throws an 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:
If I set a breakpoint on the line that loads the texture and then examine the ContentTypeReaderManager.nameToReader
member, I see this:
As you can see, a ReflectiveReader
is indeed being mapped for the Texture2D
type. This stems from my TestEntity
class (see the the entries above the one highlighted in the image above). But if you examine my model classes, nothing hanging off LevelData
has an instance of TestEntity
or even Entity
in it!
If I change the TestEntityData
class to this:
public sealed class TestEntityData : EntityData<TestEntityData, Entity>
{
}
The exception no longer occurs. That's because TestEntity
is never considered, so neither is Texture2D
. Thus, the ReflectiveReader
is looking at - and following - the generic type parameters in my model classes! I can only assume this is a bug - it makes no sense at all to me why this would be necessary.
My model classes have these generic type parameters for a good reason - they make my model code much simpler. Am I stuck here? Is my only option to refactor my models to never have a generic type parameter of my entity types? I considered using ContentSerializerIgnoreAttribute
, but that only works against properties and fields, which makes sense considering they're the only things that should influence serialization.
Anyone have any advice?
Load<Texture2D>
succeed without raising an exception? Your question is fairly clear but it's not clear how your example relates to it. I would say however that serialisation does have to look at generic types, because otherwise it can't be guaranteed to be able to reconstruct whatever it reads from the stream. \$\endgroup\$Load<Texture2D>
works if the reflective reader hasn't got in there first and claimed that it is reponsible for loading textures. If, for example, I skip the call to load my test level then the texture successfully loads using XNA'sTextureReader
or whatever it's called. I dispute that the generic parameters have any bearing on serialization. Serialization concerns itself only with the state of an object, and the object in question has no entity in it. The generic parameter is only used in methods on the object, not in data. \$\endgroup\$Type.GetGenericArguments
, whether a closed generic type or an open generic type. Maybe the docs are wrong and you are right, but the docs explain why Texture2D is covered by the Reflection system and hence show up in your serialisation code. Maybe you could ask on MSDN since it doesn't seem anybody here has a better idea. \$\endgroup\$