簡體   English   中英

C#中涉及集合,泛型和接口的設計問題

[英]A design problem involving Collections, Generics and Interfaces in C#

這篇文章包含很多代碼,但是如果您花一些時間閱讀和理解它,我將不勝感激...並希望提出解決方案

假設我正在構建一個網絡游戲,需要繪制實體,並從服務器中相應地更新其中的一些實體。

Drawable類負責在屏幕上繪制實體:

class Drawable
{
    public int ID { get; set; } // I put the ID here instead of having another 
                                // class that Drawable derives from, as to not 
                                // further complicate the example code.

    public void Draw() { }
}

從服務器接收的數據實現了IData ,每個具體的IData擁有不同的屬性。 假設我們有PlayerEnemy將收到的以下數據:

interface IData
{
    int ID { get; set; }
}

class PlayerData : IData
{
    public int ID { get; set; }
    public string Name { get; set; }
}

class EnemyData : IData
{
    public int ID { get; set; }
    public int Damage { get; set; }
}

應該可以從服務器更新的游戲實體實現IUpdateable<T> ,其中TIData

interface IUpdateable<T> where T : IData
{
    void Update(T data);
}

因此,我們的實體是DrawableUpdateable

class Enemy : Drawable, IUpdateable<EnemyData>
{
    public void Update(EnemyData data) { }
}

class Player : Drawable, IUpdateable<PlayerData>
{
    public void Update(PlayerData data) {}
}

這就是游戲的基本結構。


現在,我需要存儲這些DrawableUpdateable對象的Dictionary ,將Drawable的ID作為鍵存儲,並存儲一個包含DrawableUpdateable對象及其遠程具體IData的復雜對象:

class DataHolder<T, T1> where T:Drawable, IUpdateable<T1> where T1:IData
{
    public T Entity{ get; set;}
    public IData Data{ get; set;}

    public DataHolder(T entity, IData data)
    {
        Entity = entity;
        Data = data;
    }
}

舉例來說,假設我目前有以下實體:

var p1 = new Player();
var p1Data = new PlayerData();
var e1 = new Enemy();
var e1Data = new EnemyData();

var playerData = new DataHolder<Player, PlayerData>(p1, p1Data);
var enemyData = new DataHolder<Enemy, EnemyData>(e1, e1Data);

我現在需要有一個Dictionary持有的實體的ID作為關鍵字( p1.IDe1.ID )及其DataHolderplayerDataenemyData )和他們的價值。

類似於以下內容( 以下代碼僅顯示了我想要執行的操作,因此無法編譯 ):

 Dictionary<int, DataHolder> list = new Dictionary<int, DataHolder>();
 list.Add(p1.ID, playerData);
 list.Add(e1.ID, enemyData);

如何構建這樣的詞典?

[更新]

關於用法,我將需要執行以下操作:

 foreach (var value in list.Values)
 {
     var entity = value.Entity;
     entity.Update(value.Data);
 }

我還嘗試將DataHolder的設計更改為以下內容:

class DataHolder<T> where T:Drawable, IUpdateable<IData>
{
    public T Entity{ get; set;}
    public IData Data{ get; set;}

    public DataHolder(T entity, IData data)
    {
        Entity = entity;
        Data = data;
    }
}

然后,我嘗試了以下操作:

var playerData = new DataHolder<Player>(p1, p1Data);  //error

但這會引發編譯時錯誤:

類型“ Player”必須可轉換為“ IUpdateable <IData>”,才能在通用類“ DataHolder <T>”中用作參數“ T”

出於什么原因拋出? Player實現IUpdateable<PlayerData>PlayerData實現IData 這是差異問題嗎? 周圍有什么辦法嗎?

使您的DataHolder類實現或繼承一個非泛型類或接口,然后制作該(非泛型)類型的字典。

您編寫此代碼的方式沒有理由不聲明字典:

Dictionary<int, object> list = new Dictionary<int, object>();

DataHolder除了構造函數外沒有其他公共方法/屬性,因此在構造實例之后,它是無用的類型。 沒有比object更好

如果確實打算對它們進行公共操作,並且它們的參數和返回值不需要模板值,則可以從DataHolder提取非模板接口,並使其成為字典的值類型。

我認為您可能首先需要創建一個新類。

class DrawableUpdatable<T> : Drawable, IUpdatable<T> {
    public void Update() { base.Update(); }
}

讓您的PlayerEnemy從中派生,您的DataHolder會將其用作第一個通用約束。 這將允許您定義一個新的DataHolder<DrawableUpdatable<IData>, IData> 您將無法另外創建一個對象,因為EnemyPlayer都不會派生單個對象。 (您不能說DataHolder<Drawable **and** IUpdatable<IData>, IData>

我認為您的設計從一開始就太過復雜了-它會讓所有研究和維護它的新開發人員感到恐懼。 在考慮使用更復雜的代碼修復它之前,我會考慮對其進行修改。

請仔細閱讀所有代碼。 我認為這將為您提供所需的東西。 首先,您需要一個IDrawable接口

interface IDrawable { int ID { get; set; } }

再加上明顯的class Drawable : IDrawable { .. }則需要一個IEntity接口

interface IEntity : IDrawable, IUpdatetable<IData>  {     }

連同實現

class Enemy : Drawable, IEntity
{
    public Enemy(int id) : base(id) { }
    public void Update(EnemyData data)
    { ... }
    void IUpdatetable<IData>.Update(IData data)
    {
        Update(data as EnemyData);
    }
}
class Player : Drawable, IEntity
{
    public Player(int id) : base(id) { }
    public void Update(PlayerData data)
    { ... }
    void IUpdatetable<IData>.Update(IData data)
    {
        Update(data as PlayerData);
    }
}

然后,您需要通用化DataHolder

interface IDataHolder
{
    IEntity Entity { get; set;  }
    IData Data { get;  set;  }
}
class DataHolder<T,T1> : IDataHolder
    where T : class, IEntity
    where T1 : class, IData
{
    T entity;
    T1 data;
    public DataHolder(T entity, T1 data)
    {
        this.entity = entity;
        this.data = data;
    }
    public T Entity { get { return entity; } set { entity = value; } }
    public T1 Data { get { return data; } set { data = value; } }
    IEntity IDataHolder.Entity
    {
        get { return entity; }
        set { entity = value as T; }
    }
    IData IDataHolder.Data
    {
        get { return data; }
        set { data = value as T1; }
    }
}

最后使用KeyedCollection來保存所有內容並公開keys屬性

public class DataBag : KeyedCollection<int, IDataHolder>
{
    protected override int GetKeyForItem(IDataHolder item)
    {
        return item.Entity.ID;
    }
    public ICollection<int> Keys
    {
        get { return Dictionary.Keys; }
    }
}

這是測試代碼:

    public void TestCode()
    {
        Player p1 = new Player(100);
        Enemy e1 = new Enemy(1);

        PlayerData p1data = new PlayerData(11, "joe");
        EnemyData e1data = new EnemyData(12, 1000);

        DataHolder<Player, PlayerData> bag1 
            = new DataHolder<Player, PlayerData>(p1, p1data);
        DataHolder<Enemy, EnemyData> bag2 
            = new DataHolder<Enemy, EnemyData>(e1, e1data);

        Dictionary<int, IDataHolder> list = new Dictionary<int, IDataHolder>();
        list.Add(p1.ID, bag1);
        list.Add(e1.ID, bag2);


        foreach (int id in list.Keys )
        {
            IDataHolder item = list[id];
            // you can do this here: 
            // item.Entity.Update(item.Data);
            // or get the type specific version below:
            if (item.Entity is Player)
            {
                Player player = item.Entity as Player;
                PlayerData pdata = item.Data as PlayerData;
                player.Update(pdata);
                Console.WriteLine("ID={0} PlayerName={1} DataId={2}", 
                    player.ID, pdata.Name, pdata.ID);
            }
            else if (item.Entity is Enemy)
            {
                Enemy enemy = item.Entity as Enemy;
                EnemyData edata = item.Data as EnemyData;
                enemy.Update(edata);
                Console.WriteLine("ID={0} EnemyDamage={1} DataId={2}", 
                    enemy.ID, edata.Damage, edata.ID);
            }
        }
    }

它帶着微笑編譯並產生輸出

ID=100 PlayerName=joe DataId=11

ID=1 EnemyDamage=1000 DataId=12

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM