Mengapa ContentManager XNA mengikuti parameter tipe umum untuk keperluan serialisasi?

8

Akhirnya saya sampai pada masalah dan saya bertanya-tanya apa jalan terbaik saya. Singkatnya, masalahnya adalah bahwa XNA ReflectiveReadermerefleksikan ke dalam parameter tipe generik, bahkan jika tidak ada instance dari tipe generik yang disimpan dalam objek yang serial.

Contoh terbaik menunjukkan ini. Pertimbangkan kelas model berikut:

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

Sekarang anggaplah saya ingin mendefinisikan instance LevelData di dalam file XML untuk kemudian dimuat dengan 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>

Sekarang pertimbangkan logika muat sederhana ini:

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

Baris pertama berhasil, tetapi yang kedua melempar pengecualian:

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: 

Jika saya menetapkan breakpoint pada garis yang memuat tekstur dan kemudian memeriksa ContentTypeReaderManager.nameToReaderanggota, saya melihat ini:

masukkan deskripsi gambar di sini

Seperti yang Anda lihat, a ReflectiveReadermemang sedang dipetakan untuk Texture2Djenisnya. Ini berasal dari TestEntitykelas saya (lihat entri di atas yang disorot pada gambar di atas). Tetapi jika Anda memeriksa kelas model saya, tidak ada yang menggantung yang LevelDatamemiliki instance TestEntityatau bahkan Entitydi dalamnya!

Jika saya mengubah TestEntityDatakelas menjadi ini:

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

Pengecualian tidak lagi terjadi. Itu karena TestEntitytidak pernah dipertimbangkan, begitu juga tidak Texture2D. Jadi, ReflectiveReadersedang melihat - dan mengikuti - parameter tipe generik di kelas model saya! Saya hanya dapat berasumsi bahwa ini adalah bug - tidak masuk akal sama sekali bagi saya mengapa ini perlu.

Kelas model saya memiliki parameter tipe umum ini untuk alasan yang baik - mereka membuat kode model saya lebih sederhana. Apakah saya terjebak di sini? Apakah satu-satunya pilihan saya untuk memperbaiki model saya agar tidak pernah memiliki parameter tipe generik dari tipe entitas saya? Saya mempertimbangkan untuk menggunakan ContentSerializerIgnoreAttribute, tetapi itu hanya bekerja terhadap properti dan bidang, yang masuk akal mengingat mereka satu-satunya hal yang harus memengaruhi serialisasi.

Adakah yang punya saran?

saya--
sumber
Saya tidak terbiasa dengan XNA, tetapi jika Anda menghapus Texture2D dari pertimbangan, bagaimana bisa Load<Texture2D>berhasil tanpa menaikkan pengecualian? Pertanyaan Anda cukup jelas tetapi tidak jelas bagaimana contoh Anda terkait dengan itu. Namun saya akan mengatakan bahwa serialisasi memang harus melihat tipe generik, karena kalau tidak serialisasi tidak dapat dijamin dapat merekonstruksi apa pun yang dibaca dari stream.
Kylotan
Memanggil Load<Texture2D>berfungsi jika pembaca reflektif belum masuk ke sana terlebih dahulu dan mengklaim bahwa itu bertanggung jawab untuk memuat tekstur. Jika, misalnya, saya melewatkan panggilan untuk memuat tingkat pengujian saya maka tekstur berhasil dimuat menggunakan XNA TextureReaderatau apa pun namanya. Saya membantah bahwa parameter generik ada hubungannya dengan serialisasi. Serialisasi hanya berkaitan dengan keadaan suatu objek, dan objek yang dimaksud tidak memiliki entitas di dalamnya. Parameter generik hanya digunakan dalam metode pada objek, bukan pada data.
saya
@ user13414, serialisasi perlu tahu persis apa jenis objeknya untuk membuatnya kembali di ujung lain - akan ada konstruktor untuk memanggil, misalnya. Dan jenis objek termasuk argumen spesifik yang dilewatkan sebagai parameter generik, setidaknya dalam bahasa seperti C # dan C ++ (mungkin tidak di Jawa, yang mengimplementasikan generik agak berbeda).
Kylotan
@ Silyl: kelas dasar generik, bukan subclass (yang merupakan objek serial). Ini adalah tipe generik tertutup, bukan tipe terbuka.
saya
2
Dokumen yang saya tautkan dengan menyatakan bahwa .NET menyimpan informasi tentang tipe generik mengenai parameter tipe mereka, dan ini dapat diperoleh melalui Type.GetGenericArguments, apakah tipe generik tertutup atau tipe generik terbuka. Mungkin dokumen salah dan Anda benar, tetapi dokumen menjelaskan mengapa Texture2D dicakup oleh sistem Refleksi dan karenanya muncul dalam kode serialisasi Anda. Mungkin Anda bisa bertanya pada MSDN karena sepertinya tidak ada orang di sini yang memiliki ide yang lebih baik.
Kylotan

Jawaban:

4

Meskipun memang benar bahwa secara umum , serialisasi tidak harus berkaitan dengan jenis objek yang dipertanyakan dan hanya mencatat representasi negara mereka ... tidak semua implementasi serialisasi melakukan itu. Sebagian besar built-in NET metode serialisasi lakukan mencatat informasi tentang jenis berpartisipasi dalam serialisasi. Ada keuntungan untuk pilihan itu (memungkinkan untuk validasi yang lebih kuat) serta kerugian (ukuran objek serial yang lebih besar), tetapi itu tidak salah per se dan Anda hanya harus hidup dengannya.

Pipeline konten XNA, untuk tipe Anda, melintasi grafik properti serializable (dan bidang) dan membuat pembaca untuknya. Anda dapat melihat perilaku ini jika Anda memeriksa inisialisasi untuk ReflectiveReader<T>( Initializemetode, bukan konstruktor). Ini dilakukan melalui refleksi, bukan berdasarkan data aktual dalam XML (sekali lagi, ini dapat diverifikasi dengan melihat kode yang direfleksikan). Jadi tidak masalah jika ada referensi ke tekstur dalam data Anda atau tidak, jika ada Texture2Dproperti dalam grafik properti tipe itu, itu akan mencoba untuk membuat pembaca untuk itu sebagai bagian dari inisialisasi pipa konten.

Anda tidak seharusnya menggunakan referensi langsung ke Texture2Dobjek dalam konten khusus Anda. Anda mungkin menemukan utas ini (atau yang ini , sedikit banyak). Solusi yang diduga untuk masalah ini adalah menggunakan referensi eksternal Texture2DContentsebagai gantinya.


sumber