这篇文章包含很多代码,但如果您能花一些时间阅读和理解它,我将非常感激......并希望能提出一个解决方案
让我们假设我正在构建一个网络 -需要绘制实体并相应地从服务器更新其中一些实体的游戏。
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
是 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
但这会引发编译时错误:
“玩家”类型必须是可转换的
到“IUpdateable”为了
将其用作参数“T”
泛型类“DataHolder”
这是出于什么原因抛出的? Player
实现 IUpdateable
且 PlayerData
实现 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 ?
发布评论
评论(4)
让您的 DataHolder 类实现或继承非泛型类或接口,然后创建该(非泛型)类型的字典。
Make your
DataHolder
class implement or inherit a non-generic class or interface, then make a dictionary of that (non-generic) type.按照您编写的方式,没有理由不这样声明您的字典:
除了构造函数之外,
DataHolder
没有公共方法/属性,因此在构造实例后它是无用的类型。它并不比object
更好,如果您确实打算对它们进行公共操作,并且它们的参数和返回值不需要模板化值,那么您可以从
DataHolder中提取非模板化接口
并将其设为字典的值类型。The way you've written this, there's no reason not to declare your dictionary thusly:
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 thanobject
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.我认为您可能需要首先创建一个新课程。
让您的
Player
和Enemy
从它派生,并且您的 DataHolder 将其用作第一个通用约束。这将允许您定义一个新的DataHolder, IData>
。否则您将无法创建一个对象,因为Enemy
和Player
都不派生出单一对象。 (你不能说DataHolder, IData>
我认为你的设计一开始就过于复杂 - 它会吓跑任何新来的开发人员在用更复杂的代码修复它之前,我会考虑修改它。
I think you'll probably need to create a new class to begin with.
Have your
Player
andEnemy
derive from it, and your DataHolder use it as the first generic constraint. This will allow you to define a newDataHolder<DrawableUpdatable<IData>, IData>
. You're not going to be able to create one otherwise, because there is no single object that bothEnemy
andPlayer
derive. (You can't sayDataHolder<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.
请仔细阅读所有代码。我想这会让你得到你想要的。
首先,您需要一个
IDrawable
接口以及明显的
Drawable 类:IDrawable { .. }
那么您需要一个
IEntity
接口以及实现
然后您需要通用
DataHolder
并最后使用 KeyedCollection 来保存所有内容并公开 keys 属性,
这里是测试代码:
它微笑着编译并产生输出
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
interfacetogether with the obvious
class Drawable : IDrawable { .. }
then you need an
IEntity
interfacetogether with the implementations
Then you need to commonize the
DataHolder
and finally use a KeyedCollection to hold everything and expose the keys property
here is the test code:
and it compiles with a smile and yields the output
ID=100 PlayerName=joe DataId=11
ID=1 EnemyDamage=1000 DataId=12