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

发布于 2024-09-12 01:05:21 字数 3555 浏览 4 评论 0 原文

这篇文章包含很多代码,但如果您能花一些时间阅读和理解它,我将非常感激......并希望能提出一个解决方案

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

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 其中 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

但这会引发编译时错误:

“玩家”类型必须是可转换的 到“IUpdateable”为了 将其用作参数“T” 泛型类“DataHolder

这是出于什么原因抛出的? Player 实现 IUpdateablePlayerData 实现 IData。这是方差问题吗?有什么办法可以解决这个问题吗?

This post contains a lot of code, but I would really appreciate it if you took the some to read and understand it...and hopefully come up with a solution

Let's assume that I am structuring a network-game where the entities need to be drawn and some of them updated from the server accordingly.

The Drawable class is in charge of drawing the entities on-screen:

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

The data that is received from the server implements IData with each concrete IData holding different properties. Let's say we have the following data that the Player and Enemy will receive:

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

The game entities that should be updateable from the server implement IUpdateable<T> where T is IData:

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

Our entities are thus Drawable and Updateable:

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

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

So that's the basic structure of the game.


Now, I need to store a Dictionary of these Drawable and Updateable objects, storing the Drawable's ID as the key and a complex object that holds the Drawable and Updateable objects and their remote concrete 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;
    }
}

As an example, say I currently have the following entities:

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

I now need to have a Dictionary that holds the entities' ID as a key (p1.ID and e1.ID) and their DataHolder (playerData and enemyData) and their value.

Something like the following (the below code just shows what I want to do and thus it doesn't not compile):

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

How do I construct such a Dictionary?

[Update]

As regards usage, I will then need to be able to do the following:

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

I have also tried to change the design of DataHolder to the following:

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

Then I tried something like the following:

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

But that throws a compile-time error:

The type 'Player' must be convertible
to 'IUpdateable<IData>' in order
to use it as parameter 'T' in the
generic class 'DataHolder<T>'

For what reason is this thrown? Player implements IUpdateable<PlayerData> and PlayerData implements IData. Is this an issue with variance? And is there any way around it ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情独悲 2024-09-19 01:05:21

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

Make your DataHolder class implement or inherit a non-generic class or interface, then make a dictionary of that (non-generic) type.

℉絮湮 2024-09-19 01:05:21

按照您编写的方式,没有理由不这样声明您的字典:

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

除了构造函数之外,DataHolder 没有公共方法/属性,因此在构造实例后它是无用的类型。它并不比object更好,

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

The way you've written this, there's no reason not to declare your dictionary thusly:

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

DataHolder has no public methods/properties except for the constructor, so it is a useless type after the instance has been constructed. It is no better than object

If you do intend to put public operations on them and their arguments and return values don't require templated values, then you can extract a non-templated interface from DataHolder and make that the value type of the dictionary.

层林尽染 2024-09-19 01:05:21

我认为您可能需要首先创建一个新课程。

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

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

我认为你的设计一开始就过于复杂 - 它会吓跑任何新来的开发人员在用更复杂的代码修复它之前,我会考虑修改它。

I think you'll probably need to create a new class to begin with.

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

Have your Player and Enemy derive from it, and your DataHolder use it as the first generic constraint. This will allow you to define a new DataHolder<DrawableUpdatable<IData>, IData>. You're not going to be able to create one otherwise, because there is no single object that both Enemy and Player derive. (You can't say DataHolder<Drawable **and** IUpdatable<IData>, IData>

I think your design is way overcomplicated to begin with anyway - it'd frighten away any new developer who came to look at it and maintain it. I would consider revising it before fixing it with even more complex code.

垂暮老矣 2024-09-19 01:05:21

请仔细阅读所有代码。我想这会让你得到你想要的。
首先,您需要一个 IDrawable 接口

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

以及明显的 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

Please read carefully all the code. I think this will get you what you want.
First you need a IDrawable interface

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

together with the obvious class Drawable : IDrawable { .. }
then you need an IEntity interface

interface IEntity : IDrawable, IUpdatetable<IData>  {     }

together with the implementations

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

Then you need to commonize the 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; }
    }
}

and finally use a KeyedCollection to hold everything and expose the keys property

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

here is the test code:

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

and it compiles with a smile and yields the output

ID=100 PlayerName=joe DataId=11

ID=1 EnemyDamage=1000 DataId=12

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文