I'm trying to find a good, clean design pattern or commonly accepted implementation to deal with an enumeration of types where the individual type is known only at runtime.
I know similar questions have been asked before, and I've read through as many answers as I can find, but I'm not sure I'm doing any better than the simple switch statement.
In my application I send and receive data over a stream. At run time, I receive a data structure via serialization that describes what fields are located within my binary data. This includes the Type of the data in the field, i.e. Int32, Bool, Double, etc. At design time, all I know is that the data may be in one of several types.
Because I can't switch on Type, I've created an enumeration of types that I can receive:
enum RawDataTypes
{
Int32,
Int16,
Double,
Single,
etc.
}
My data structure tells me which type is in which field. Now, when reading the data, when I read the field identifier, I can then get the type. From there, I use a switch to read the data itself:
object ReadDataField(byte [] buff, ref int position,
Dictionary<int, RawDataTypes> fields)
{
object value;
int field = (int)BitConverter.ToUInt32(buff, 0);
switch(fields[field])
{
case RawDataTypes.Int32:
{
value = (int)BitConverter.ToInt32(buff, position);
position+=sizeof(int);
break;
}
case RawDataTypes.Int16:
etc...
}
return value;
}
While this approach works, it doesn't feel quite right, and I'm trying to find a better solution than what I have. It seems like I should be able to use the raw data types and cast to the type I want. I've read in several places that we can't switch on a Type for a reason, that there are better solutions out there. I'm going to walk through my trial-and-error process and show what I've come up with, and then question whether it's really any better than the original solution?
This thing that has been driving my solution is needing to read various data types from a byte array. I'm doing with with BitConverter. Unfortunately, all the BitConverter methods are type specific, such as BitConverter.ToInt32(). There doesn't seem to be a BitConverter.ToObject(Type, byte [], int). So deriving my object from a generic class doesn't seem like it will help me.
Here's what I've done so far:
I have used a nice solution provided as a solution to someone else's question to add attributes to my enumeration so that I can get the type, like this:
enum RawDataTypes
{
[RawDataType(typeof(Int32))]
Int32,
[RawDataType(typeof(Int16))]
Int16,
[RawDataType(typeof(Double))]
Double,
[RawDataType(typeof(Single))]
Single,
etc.
}
So that I can easily get the type from the fields dictionary using an extension method on the enumeration like this:
public static Type Type(this RawDataField.RawDataTypes rawDataType)
{
Type type;
if (rawDataTypes.TryGetValue(rawDataType, out type))
return type;
else
throw new ArgumentException(
String.Format("No type defined for RawDataFields.{0}.",
rawDataType));
}
Then, I wrote extension methods for each of the raw data types that I may receive, such as the following:
public static Int32 ReadRawData(Int32 value, byte[] buff, ref int position)
{
value = BitConverter.ToInt32(buff, position);
position += sizeof(Int32);
return value;
}
I was hoping to do something like this:
object ReadDataField(byte [] buff, int position, Dictionary<int, RawDataTypes> fields)
{
int field = (int)BitConverter.ToUInt32(buff, 0);
object value = Activator.CreateInstance(fields[field].Type());
value.ReadRawData(buff, ref position);
return value;
}
But of course that doesn't work, as the compiler complains:
'object' does not contain a definition for 'ReadRawData' and the best extension method overload 'RawDataFieldExtensions.ReadRawData(short, byte[], ref int)' has some invalid arguments in blah blah...
However, if I leave out that line and run it, after the Activator line if I call object.GetType() it returns System.Int32.
Aha!! Time to use the "dynamic" keyword!
So I tried declaring value as dynamic. This compiles, but then fails at runtime because it can't find the extension method. And then I learned that you can't use extension methods on dynamic types. Ohhhh kay...
So then I tried using argument polymorphism to get there from here. I added another extension method to my enumeration and several methods like this:
public static Object ReadRawData (this RawDataField.RawDataTypes rawDataType,
byte[] buff, ref int position)
{
object newValue = Activator.CreateInstance(rawDataType.Type());
ReadRawDataField(ref newValue, buff, ref position);
return newValue;
}
internal static void ReadRawDataField(ref Int16 value, byte[] buff,
ref int position)
{
value = BitConverter.ToInt16(buff, position);
position += sizeof(Int16);
}
But again, the compiler complains:
Argument 1: cannot convert from 'ref object' to 'ref short'
Alright, fine. Dynamic?
public static Object ReadRawData (this RawDataField.RawDataTypes rawDataType,
byte[] buff, ref int position)
{
dynamic newValue = Activator.CreateInstance(rawDataType.Type());
RawDataFieldExtensions.ReadRawData(ref newValue, buff, ref position);
return newValue;
}
Nope.
Argument 1: cannot convert from 'ref dynamic' to 'ref short'
Switch is looking pretty good right about now! lol. OK, so no dynamic by reference, I guess... So I'll pass the object without the ref keyword and return another one. At this point, doesn't it seem like I'm implementing a lot of workarounds to avoid using the simple switch?
Anyway, finally, this is what I have that works:
public static Object ReadRawData (this RawDataField.RawDataTypes rawDataType,
byte[] buff, ref int position)
{
dynamic newValue = Activator.CreateInstance(rawDataType.Type());
newValue = RawDataFieldExtensions.ReadRawData(newValue, buff, ref position);
return newValue;
}
internal static Int16 ReadRawDataField(Int16 value, byte[] buff,
ref int position)
{
value = BitConverter.ToInt16(buff, position);
position += sizeof(Int16);
return value;
}
internal static Int32 ReadRawDataField(Int32 value, byte[] buff,
ref int position)
{
value = BitConverter.ToInt32(buff, position);
position += sizeof(Int32);
return value;
}
etc.
And I'm able to call it like this:
object ReadDataField(byte [] buff, int position, Dictionary<int, RawDataTypes> fields)
{
int field = (int)BitConverter.ToUInt32(buff, 0);
object newValue = fields[field].RawDataType.ReadRawData(buff, ref position);
return value;
}
So here I am... I've gone in circles a couple of times and came up with the above solution that makes this work without having a switch statement or a series of if-thens to cast my object into a specific type. I've had to add quite a bit of code (well, replace chunks of code with other chunks of code), and I'm not convinced this solution is any better. One routine is much more succinct, but there's an equivalent amount of code hidden elsewhere. It kinda seems like 6 of one, a half-dozen of the other.
Is there a better way? Or in this instance, is a switch just the way to go? Or is there something magical about using method overloads that makes it so much better than switch that I just don't get?
Thanks for taking the time to read through this! Please let me know if I can provide any more details. And please note, the code above isn't a direct copy/paste; it's the relevant portions of the routines cut down for clarity.
Aucun commentaire:
Enregistrer un commentaire