Rose Kunkel
Rose Kunkel

Reputation: 3268

Operating on Multi-type Container Without Knowing the Type

I'm implementing a sort of networked multi-type hash table in C#, and I've run into a few problems. Each entry has some metadata associated with it, like a string name and an id number. There are places in my code where I'm operating on entries, but I don't necessarily need to know the type that they contain. So, for example, when parsing update packets, I'd like to be able to do something like this:

entry.Name = ParseName(data);
entry.Id = ParseId(data);
entry = ParseValue(entry, data);
table.Insert(entry);

Where ParseValue() takes the entry, sets some sort of "Value" parameter and some sort of "Type" parameter based on the data I've gotten from the network, and returns a generic entry. Then, when I want to do something with the data, I'd like to be able to do something like switching over entry.Type and then using the value by casting it to entry.Type. But every time I've tried to do something like this, I end up needing to put type parameters in places where I don't know the type. I know that the type is one of 6 possible types (bool,double,string,List<bool>,List<double>, or List<string>), but I don't know which of those types it is. Is there a way I can do this in C#, or even better, is there a way I can change my design so that this isn't an issue?

Note: All the code above is a simplified representation of what I'm doing because I didn't want to get into the messy bit twiddling I needed to do to parse these packets.

Edit: An example of how I might try to use this (I know this isn't valid C#)

foreach( entry in table ) {
    switch( entry.Type ) {
        case typeof(bool):
            displayBool( (bool)(entry.Value) );
            break;
        case typeof(double):
            displayDouble( (double)(entry.Value) );
            break;
        case typeof(string):
            displayString( (string)(entry.Value) );
            break;
        case typeof(List<bool>):
            displayListOfBool( (List<bool>)(entry.Value) );
            break;
        case typeof(List<double>):
            displayListOfDouble( (List<double>)(entry.Value) );
            break;
        case typeof(List<string>):
            displayListOfStrings( (List<string>)(entry.Value) );
            break;
    }
}

Edit 2: If it helps, the spec for what I'm trying to implement is here: http://firstforge.wpi.edu/sf/docman/do/downloadDocument/projects.wpilib/docman.root/doc1318

Upvotes: 0

Views: 219

Answers (2)

hBGl
hBGl

Reputation: 294

I like Vadim's approach. You could make that work for you like that:

abstract class BaseEntry
{
    public abstract object Value { get; }
    public string Name { get; set; }

    public abstract void Display();
    public abstract void SetValue(object value);
}

And you can implement it like this:

class BoolEntry : BaseEntry
{
    public bool ConcreteValue { get; protected set; }

    public override object Value
    {
        get
        {
            return this.ConcreteValue;
        }
    }

    public override void SetValue(object value)
    {
        this.ConcreteValue = (bool)value;        
    }

    public void SetValue(bool value)
    {
        this.ConcreteValue = value; 
    }

    public override void Display()
    {
        Console.WriteLine("Here is your bool: " + this.ConcreteValue.ToString());
    }
}

Instead of implementing a type constraint in the Entry classes You can build a hash table that only allows values of given types. Here is an example:

class ConstraintHashTable<TKey, TValue> // Implement all the good interfaces
{
    private HashSet<Type> AllowedTypes;
    private Dictionary<TKey, TValue> Dictionary;

    public TValue this[TKey key]
    {
        get
        {
            return this.Dictionary[key];
        }
    }

    public ConstraintHashTable(HashSet<Type> allowedTypes)
    {
        this.AllowedTypes =
            allowedTypes == null ? new HashSet<Type>() : allowedTypes;

        this.Dictionary = new Dictionary<TKey, TValue>();
    }

    public void Add(TKey key, TValue value)
    {
        if (this.AllowedTypes.Contains(value.GetType()) == false)
        {
            throw new ArgumentException(
                "I don't accept values of type: " + value.GetType().FullName + ".");
        }
        this.Dictionary.Add(key, value);
    }
}

Then you can create a hash table that takes only BoolEntry values but saves it as BaseEntry like this:

var allowedTypes = new HashSet<Type>(new Type[] { typeof(BoolEntry) });
var cht = new ConstraintHashTable<string, BaseEntry>(allowedTypes);

cht.Add("001", new BoolEntry());
cht["001"].SetValue(true);   
cht["001"].Display();
cht["001"].SetValue(false);
cht["001"].Display();

Upvotes: 1

Vadim
Vadim

Reputation: 2865

Every time you see something like

 switch( entry.Type )

It's time to think about Polymorphism.

Suppose (for the sake of simplicity) that you wish to do something like

switch( entry.Type ) 
{
    case typeof(bool):
        displayBool( (bool)(entry.Value) );
        break; 
    case typeof(double):
        displayDouble( (double)(entry.Value) );
        break;
}

I think it would be much cleaner to write

entry.Display();

And have this done via inheritance and polymorphism. Then you'll have a generic base abstract class and other classes that implement different Display() - one for each actual type you're using.

abstract class BaseEntry<T>
{
   public T Value {get; set;}
   public string Name {get; set;}

   public abstract void Display();
}

class BoolEntry : BaseEntry<bool>
{
     public override void Display()
     {
         // Your code here
     }
}

If Display() is not something Entry should do, you can give it a reference to something other object that's capable of that sort of thing.

Upvotes: 3

Related Questions