涉及C#中的集合,泛型和接口的设计问题

时间:2010-07-27 17:34:35

标签: c# generics collections interface covariance

这篇文章包含很多代码,但是如果你花一些时间阅读并理解它,我会非常感激...并希望能找到一个解决方案

让我们假设我正在构建一个网络游戏,其中需要绘制实体,并相应地从服务器更新其中一些实体。

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) {}
}

这就是游戏的基本结构。


现在,我需要存储这些DictionaryDrawableUpdateable个对象,将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

但是这会引发编译时错误:

  

“玩家”类型必须是可转换的   'IUpdateable&lt; IData&gt;'为了   将它用作参数'T'   泛型类'DataHolder&lt; T&gt;'

这是什么原因引起的? Player实施IUpdateable<PlayerData>PlayerData实施IData。这是一个有差异的问题吗?它有什么办法吗?

4 个答案:

答案 0 :(得分:2)

使您的DataHolder类实现或继承非泛型类或接口,然后创建该(非泛型)类型的字典。

答案 1 :(得分:1)

你写这篇文章的方式,没有理由不这样声明你的字典:

Dictionary<int, object> list = new Dictionary<int, object>();
除了构造函数之外,

DataHolder没有公共方法/属性,因此在构造实例后它是无用的类型。它并不比object

如果您打算对它们进行公共操作并且它们的参数和返回值不需要模板化值,那么您可以从DataHolder中提取非模板化接口并使其成为字典的值类型

答案 2 :(得分:1)

我认为你可能需要先创建一个新类。

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

PlayerEnemy从中派生出来,并且您的DataHolder将其用作第一个通用约束。这将允许您定义新的DataHolder<DrawableUpdatable<IData>, IData>。否则你将无法创建一个,因为EnemyPlayer都没有单个对象。 (你不能说DataHolder<Drawable **and** IUpdatable<IData>, IData>

我认为你的设计无论如何都要过于复杂 - 它会吓跑任何来看它并维护它的新开发者。在使用更复杂的代码修复它之前,我会考虑修改它。

答案 3 :(得分:1)

请仔细阅读所有代码。我想这会得到你想要的东西。 首先,您需要一个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