7

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, but it's still not clear to me that the alternate implementations have significant advantages over a switch, or a series of if-thens.

First, I'm going to demonstrate a few implementations, and then I'm going to ask the question: Are these implementations better than or preferred over the simple switch? If so, why? If not, why not?

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. I need to read the fields from the stream and deal with the data appropriately.

If switching on Types were allowed, a solution could be as follows:

Non-Working Code:

object ReadDataField(byte [] buff, ref int position, Dictionary<int, Type> fields) { object value; int field = buff[position]; position++; switch(fields[field]) { case typeof(Int32): { value = (Int32)BitConverter.ToInt32(buff, position); position += sizeof(Int32); break; } case typeof(Int16): { value = (Int16)BitConverter.ToInt16(buff, position); position += sizeof(Int16); break; } // Etc... } return value; } 

In my opinion, this code has the advantage of being straightforward, easy to read, and simple to maintain.

However, as switching on Types is not available in C# I implemented the above as follows:

Working Code:

enum RawDataTypes { Int32, Int16, Double, Single, etc. } object ReadDataField(byte [] buff, ref int position, Dictionary<int, RawDataTypes> fields) { object value; int field = buff[position]; position++; switch(fields[field]) { case RawDataTypes.Int32: { value = (int)BitConverter.ToInt32(buff, position); position += sizeof(int); break; } case RawDataTypes.Int16: { value = (Int16)BitConverter.ToInt16(buff, position); position += sizeof(Int16); break; } // Etc. } return value; } 

This is clearly a work-around, but it is also straightforward and easy to maintain.

However, there are several articles detailing switching on Types is not available in C#. And besides the difficulty dealing with inheritance in manner that yields an expected result, etc., I've seen many answers that have said there is a much "better" approach that is more in line with the spirit of object oriented programming.

The common solutions proposed are 1) use polymorphism, or 2) use a dictionary lookup. But implementing either has its own challenges.

Regarding polymorphism, the following is an example of "wouldn't it be nice if it worked" code:

Non-Working Implementation of Polymorphism:

object ReadDataField(byte [] buff, int position, Dictionary<int, Type> fields) { int field = buff[position]; position++; object value = Activator.CreateInstance(fields[field]); // Here we're trying to use an extension method on the raw data type. value.ReadRawData(buff, ref position); return value; } public static Int32 ReadRawData(this Int32 value, byte[] buff, ref int position) { value = BitConverter.ToInt32(buff, position); position += sizeof(Int32); return value; } public static Int16 ReadRawData(this Int16 value, byte[] buff, ref int position) { value = BitConverter.ToInt16 (buff, position); position += sizeof(Int16 ); return value; } // Additional methods for each type... 

If you try to compile the above code you'll get:

'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...

You can't subclass the raw data types to add the functionality, because they're sealed, so extension methods seemed like an option. However, the extension methods won't convert from 'object' to the actual type, even though calling value.GetType() returns the underlying type: System.Int32, System.Int16, etc. Using the 'dynamic' keyword doesn't help, either, because you can't use extension methods on a dynamic type.

The above can be made to work by passing an instance of the object itself as a parameter to methods with polymorphic parameters:

Working Implementation of Polymorphism:

object ReadDataField(byte [] buff, int position, Dictionary<int, Type> fields) { int field = buff[position]; position++; dynamic value = Activator.CreateInstance(fields[field]); // Here the object is passed to an overloaded method. value = ReadRawData(value, buff, ref position); return value; } public static Int32 ReadRawData(Int32 value, byte[] buff, ref int position) { value = BitConverter.ToInt32(buff, position); position += sizeof(Int32); return value; } public static Int16 ReadRawData(Int16 value, byte[] buff, ref int position) { value = BitConverter.ToInt16 (buff, position); position += sizeof(Int16 ); return value; } // Additional methods for each type... 

The above code works and is still straightforward and maintainable, and probably more "in the spirit of object oriented programming."

But is it really any "better?" I would argue that it makes it more difficult to maintain, as it requires more searching to see which types have been implemented.

An alternate approach is to use a dictionary lookup. Such code might look like this:

Dictionary Implementation:

delegate object ReadDelegate(byte [] buff, ref int position); static Dictionary<Type, ReadDelegate> readers = new Dictionary<Type, ReadDelegate> { { typeof(Int32), ReadInt32 }, { typeof(Int16), ReadInt16 }, // Etc... }; object ReadDataField(byte [] buff, int position, Dictionary<int, Type> fields) { int field = buff[position]; position++; object value = readers[fields[field]](buff, ref position); return value; } public static object ReadInt32(byte[] buff, ref int position) { Int32 value = BitConverter.ToInt32(buff, position); position += sizeof(Int32); return value; } public static object ReadInt16(byte[] buff, ref int position) { return BitConverter.ToInt16(buff, position); position += sizeof(Int16); return value; } // Additional methods for each type... 

An advantage of the dictionary implementation, in my opinion, over the polymorphic solutions is that it lists all of the types that can be handled in one easy to read location. This is useful for maintainability.

However, given these examples, are there any better, cleaner, more accepted, etc. implementations that have significant advantage over the above? Are these implementations using polymorphism or a dictionary lookup preferred over using a switch? I'm not really saving any code, and I'm not sure I've increased the maintainability of the code at all.

In any case, I still need to enumerate each of the types with its own method. Polymorphism is deferring the conditional to the language itself, rather than being explicit with a switch or an if-then. Using a dictionary is relying on the internal conditionals to do its own lookup. At the end of the day, what's the difference?

6
  • Holy smokes, that's a broadly-written question. Yes, polymorphism is the usual way to avoid having to switch on types, check for specific types, etc. Please narrow the question so that it can be answered in a specific, clear way. See stackoverflow.com/help/how-to-ask Commented Feb 18, 2015 at 7:29
  • 2
    I love questions narrated like stories :) It presents whole thinking process and leads through all possibilities. Just my thought. Commented Feb 18, 2015 at 7:48
  • @PeterDuniho, I rewrote my question to narrow it down a bit. However, I wanted to incorporate several implementation examples and the thinking behind them. Commented Feb 18, 2015 at 19:55
  • 1
    @Fka, I had to update my question based on the other comments, so I think I've lost some of the thinking process behind it, and in that regard, something is lost... I was trying to convey the idea that there is an ideal about object oriented programming that, when it comes down to real implementation, often fails to live up to that ideal. Commented Feb 18, 2015 at 19:57
  • 3
    For what it's worth, when the BCL classes need to switch on type, they use Type.GetTypeCode(). Object-oriented principles are all well and good, but when you're switching on primitive types, what you're often after is speed. Commented Feb 18, 2015 at 21:31

2 Answers 2

3
  1. Use a static collection of 'Converters' (or whatever) that all implement a common interface. Then you can iterate over that collection asking each if they handle the type. If they do, then ask them to do it. Each converter only knows about its type.
  2. Use the same static 'collection' of converters but hold them in a Dictionary keyed by type. Then request the converter by type from the dictionary and ask it to convert for you.
Sign up to request clarification or add additional context in comments.

7 Comments

I like 2. The collection elements could be delegates, defined on the fly since they are one-liners (basically Convert() calls).-- Often, the answer to a switch is a Dictionary indexed by the switch criteria. It offloads the complexity from the code to the data. Whether that's more readable or maintainable is open for debate -- there are few things easier to understand and maintain than a nice old-school switch. But as soon as you program a second switch somewhere it's time to store the information in the data.
Yes, a delegate rather than an instance of a 'converter' is an interesting idea that I'd not considered. This is something I might be able to use elsewhere. Thanks for the idea @Peter Schneider.
Thanks for your input, guys. In my best estimation, I have incorporated your ideas into my question above. First, am I capturing what you're suggesting? And second, if I am, what, in your opinion, makes this better than the simple conditionals? @PeterSchneider, your comments about readability and maintainability of a switch and "as soon as you program a second switch somewhere it's time to store the information in the data" is making me think! :)
@PeterSchneider is bang on with the comment about the second switch. Polymorphism really helps when you need to make the decision based on type in many places as you can encode it in the object and the behaviour is used wherever and whenever you need it. The benefit is large and tangible, especially from a maintenance point of view. If you have a single place that you do the lookup and make the decision and everything uses that place (like a factory method) then the benefits of polymorphism are much less obvious, and a switch might be simpler and more maintainable
What makes it better than using a switch, IMHO, is that the code doing the work never needs to change. You only change the list of converters. This gets you close to full OCP compliance.
|
0

Use the Strategy design pattern:

Define separate converter objects (with a common interface) that encapsulate the different converting algorithms. Clients delegate the converting to the proper converter object at run-time.

This greatly reduces implementation dependencies. Client code is independent of how the converting is implemented.

I agree with @David Osborne's answer. And even if you implement a single converter object with a switch statement, this implementation is encapsulated and hidden from clients.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.