Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
282 views
in Technique[技术] by (71.8m points)

c# - Design Pattern or Accepted Solutions for Avoiding Switching on Types

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
  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.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...