[英]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
擁有不同的屬性。 假設我們有Player
和Enemy
將收到的以下數據:
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>
,其中T
是IData
:
interface IUpdateable<T> where T : IData
{
void Update(T data);
}
因此,我們的實體是Drawable
和Updateable
:
class Enemy : Drawable, IUpdateable<EnemyData>
{
public void Update(EnemyData data) { }
}
class Player : Drawable, IUpdateable<PlayerData>
{
public void Update(PlayerData data) {}
}
這就是游戲的基本結構。
現在,我需要存儲這些Drawable
和Updateable
對象的Dictionary
,將Drawable
的ID作為鍵存儲,並存儲一個包含Drawable
和Updateable
對象及其遠程具體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.ID
和e1.ID
)及其DataHolder
( playerData
和enemyData
)和他們的價值。
類似於以下內容( 以下代碼僅顯示了我想要執行的操作,因此無法編譯 ):
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(); }
}
讓您的Player
和Enemy
從中派生,您的DataHolder會將其用作第一個通用約束。 這將允許您定義一個新的DataHolder<DrawableUpdatable<IData>, IData>
。 您將無法另外創建一個對象,因為Enemy
和Player
都不會派生單個對象。 (您不能說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.