views:

124

answers:

4

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 ?

+2  A: 

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

SLaks
Agreed. Why not just have your DataHolder class implement IData where the return value for DataHolder.ID is just DataHolder.Data.ID then you don't need your Data object to add a DataHolder to your dictionary as well. Your Dictionary will become Dictionary<int, IData> and Your list.Add(p1.ID, playerData); will become list.Add(playerData.ID, playerData);
Jack
+1  A: 

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.

David Gladfelter
A: 

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.

Mark H
+1  A: 

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

jalexiou
Why would you want two identical interfaces IData and IDrawable if anything make it more generic and call it IIdentifiable and have just one. Also Since you have IEntity and IData does DataHolder really need to be generic at this point?
Jack
The code is truncated. Although IData and IDrawable implement an ID propertly the design intent (as I understand it from the original posting) would include many other members also. If I commonise those then the ID for Player and the ID for PlayerData are going to have to be the same number (which the poster does not want). DataHolder may not be generic, but the original posting wanted to show a distinct types in the dictionary collection `list` and thus the use of `bag1 = DataHolder<Player,PlayerData>` with `bag1.Data.Name` returning the value without any casting.
jalexiou
No Player.ID and PlayerData.ID would not be the same number just because two classes inherit the same base class or interface does not mean they have the same data. Only if you set the PlayerData.ID to the Player.ID would you get the same ID. Granted the IDs would be the of same Type but not the same data.
Jack