Reputation: 3268
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
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
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