Почему ContentManager XNA следует параметрам универсального типа для целей сериализации?

8

Я наконец-то понял причину проблемы и мне интересно, каково мое лучшее решение. Короче говоря, проблема в том, что XNA ReflectiveReaderотражается в параметрах универсального типа, даже если ни один экземпляр этого универсального типа не сохраняется в сериализуемом объекте.

Пример лучше всего демонстрирует это. Рассмотрим следующие модельные классы:

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

Теперь предположим, что я хочу определить экземпляр LevelData внутри XML-файла для последующей загрузки с помощью 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>

Теперь рассмотрим эту простую логику загрузки:

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

Первая строка завершается успешно, но вторая создает исключение:

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: 

Если я установлю точку останова на строке, которая загружает текстуру, а затем проверим ContentTypeReaderManager.nameToReaderэлемент, я вижу это:

введите описание изображения здесь

Как вы можете видеть, ReflectiveReaderдля Texture2Dтипа действительно отображается a . Это связано с моим TestEntityклассом (см. Записи выше той, что выделена на изображении выше). Но если вы изучите мои классы моделей, ничто не висит или даже LevelDataне содержит их!TestEntityEntity

Если я изменю TestEntityDataкласс на это:

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

Исключение больше не возникает. Это потому, что TestEntityникогда не рассматривается, так что нет Texture2D. Таким образом, ReflectiveReaderмы рассматриваем и следуем за параметрами универсального типа в моих модельных классах! Я могу только предположить, что это ошибка - для меня нет никакого смысла, зачем это нужно.

Мои классы моделей имеют эти параметры универсального типа по уважительной причине - они значительно упрощают код моей модели. Я застрял здесь? Является ли мой единственный вариант реорганизации моих моделей, чтобы никогда не иметь универсального параметра типа моих типов сущностей? Я подумал об использовании ContentSerializerIgnoreAttribute, но это работает только против свойств и полей, что имеет смысл, учитывая, что это единственное, что должно влиять на сериализацию.

У кого-нибудь есть совет?

меня--
источник
Я не знаком с XNA, но если вы удалите Texture2D из рассмотрения, как можно Load<Texture2D>добиться успеха, не вызывая исключения? Ваш вопрос довольно ясен, но не ясно, как ваш пример относится к нему. Однако я бы сказал, что сериализация должна учитывать общие типы, потому что в противном случае нельзя гарантировать, что она сможет восстановить все, что читает из потока.
Kylotan
Вызов Load<Texture2D>работает, если рефлексивный ридер не попал туда первым и заявил, что он отвечает за загрузку текстур. Если, например, я пропущу вызов, чтобы загрузить свой тестовый уровень, то текстура успешно загрузится с использованием XNA TextureReaderили как там это называется. Я спорю, что общие параметры имеют какое-либо отношение к сериализации. Сериализация касается только состояния объекта, и у рассматриваемого объекта нет сущности в нем. Универсальный параметр используется только в методах объекта, а не в данных.
я--
@ user13414, сериализация должна точно знать, что это за объект, чтобы воссоздать его на другом конце - например, будут вызываться конструкторы. И тип объекта включает в себя конкретный аргумент, передаваемый в качестве универсального параметра, по крайней мере, в таких языках, как C # и C ++ (возможно, не в Java, которая реализует универсальные шаблоны несколько по-другому).
Kylotan
@Kylotan: базовый класс является общим, а не подклассом (который является сериализуемым объектом). Это закрытый универсальный тип, а не открытый.
я--
2
В документах, на которые я ссылался, указано, что в отражении .NET хранится информация об универсальных типах, касающихся параметров их типов, и ее можно получить с помощью Type.GetGenericArgumentsзакрытого универсального типа или открытого универсального типа. Возможно, документы не правы, а вы правы, но они объясняют, почему Texture2D покрывается системой Reflection, и, следовательно, отображаются в вашем коде сериализации. Может быть, вы могли бы спросить на MSDN, так как, кажется, ни у кого здесь нет лучшей идеи.
Kylotan

Ответы:

4

Хотя это правда , что в целом , сериализация не обязательно самая озабоченность с типами объектов , о которых идет речь , и только рекордное представление их состояний ... не все реализации сериализации сделать это. Большинство встроенных в .NET методов сериализации сделать запись информации о типах участвующих в сериализации. У этого выбора есть свои преимущества (допускающие более надежную проверку), а также недостатки (больший размер сериализованного объекта), но это не так само по себе, и вам просто нужно с этим жить.

Конвейер содержимого XNA для ваших типов пересекает граф сериализуемых свойств (и полей) и создает для них читатели. Вы можете увидеть это поведение, если изучите инициализацию для ReflectiveReader<T>( Initializeметод, а не конструктор). Это делается с помощью отражения, а не на основе фактических данных в XML (опять же, это можно проверить, посмотрев на отраженный код). Таким образом, не имеет значения, есть ли ссылка на текстуру в ваших данных или нет, если есть Texture2Dсвойство в графе свойств типа, оно попытается создать для него читатель как часть инициализации конвейера содержимого.

Вы не должны использовать прямые ссылки на Texture2Dобъекты в вашем пользовательском контенте. Вы можете найти эту ветку (или эту , в меньшей степени). Предполагаемое решение проблемы заключается в использовании внешних ссылок на Texture2DContent.


источник