tags:

views:

129

answers:

5

I'm trying to implement a class to access items of different types, in a similar way to database rows.

However, I have two different ideas in mind, and I don't know which one to choose:

Design 1

public enum ObjectTypeA
{
    Undefined,
    Integer,
    Float
}

public class MyObjectA
{
    private object val;

    public ObjectTypeA Type
    {
        get;
        private set;
    }

    public int Integer
    {
        get
        {
            if (Type != ObjectTypeA.Integer) throw new Exception();
            return (int)val;
        }
        set
        {
            Type = ObjectTypeA.Integer;
            val = value;
        }
    }

    public float Float
    {
        get
        {
            if (Type != ObjectTypeA.Float) throw new Exception();
            return (float)val;
        }
        set
        {
            Type = ObjectTypeA.Float;
            val = value;
        }
    }
}
  • Less compile-time checks possible.
  • Can't use the is operator, GetType(), etc. (reinvents the type system).
  • Boxing and unboxing for value types.
  • Can be inherited by other classes (e.g. I can create a "named object" using inheritance).

Design 2

public abstract class MyObjectB
{
}

public class MyIntegerB : MyObjectB
{
    public int Value
    {
        get;
        set;
    }

    public MyIntegerB(int _value)
    {
        Value = _value;
    }
}

public class MyFloatB : MyObjectB
{
    public float Value
    {
        get;
        set;
    }

    public MyFloatB(float _value)
    {
        Value = _value;
    }
}
  • Shorter and simpler implementation.
  • Very verbose (casting) to use.

Performance is not critical, but it's still important, since most of the objects that are going to be stored are integers or floats, so boxing overhead matters.

The classes will just contain the values, not methods that depend on the type, etc. so it doesn't matter if the solution uses inheritance.

IMPORTANT: One of the requirements is that there may be two types that use the same underlying type (e.g. two classes derived from MyObjectB may use int as the Value), so using object or generics may not be possible.

Any suggestion about which design to use, or another different design?

EDIT: The reason I don't like the second one is because it's very verbose to use:

MyObjectB objB = new MyIntegerB(12);
Console.WriteLine(((MyIntegerB)objB).Value);

And because I can't inherit it to create something like a "named object", so I have to attach MyObjectB to the class, and the usage is even more verbose.

+6  A: 

I don't see why you wouldn't use generics here. More strongly: I don't see why you need this at all: It seems like Nullable<T> would cover all of your use cases very nicely. If not, implementing this generically is trivial:

public class ValueWrapper<T>
{
    public T Value
    {
        get;
        private set;
    }

    public Type WrappedType
    {
        get { return typeof(T); }
    }
}

public MySpecialInt : ValueWrapper<int>
{
    /* etc */
}
JSBangs
+1 for `Nullable<T>`. If all the OP is trying to do is wrap primitives into reference types, `Nullable` (or at least **some** form of generics) is ideal.
Brian
This is the answer you are looking for, even if you don't think it is. Unless you left something out, it does fit your requirements. You seem to misunderstand Generics, or are leaving some major things out of the question.
drharris
A: 

why not use generics?

public abstract class MyObjectB<T>
{
    public T Value
    {
        get;
        set;
    }

    public MyObjectB(T _value)
    {
        Value = _value;
    }
}

you only need one class at this point. just instantiate it differently:

var myObj = new MyObjectB<Int>(1);

or

var myObj = new MyObjectB<Float>(0.012);

Derick Bailey
Your syntax is incorrect. You need a `<T>` after the class name. Also, you have to include the generic type during construction: `new MyObjectB<int>(1)` and `new MyObjectB<double>(0.12)`
Bryan Watts
my syntax was correct... stackoverflow filtered out the generics tags. updated to fix that.
Derick Bailey
A: 

Have you considered generics?

public class MyObjectA<T> { 

  public T Value { 
      get; set;
  } 

}
Jordão
A: 

I know you mentioned not wanting to deal with boxing and unboxing, but I still think a Generic class would be your best bet here.

public class MyObject<T>
{
    public MyObject(T t) {
        Value = t;
    }

    public T Value { get; set; }
}

Edit:

One of the requirements is that there may be two types that use the same underlying type (e.g. two classes derived from MyObjectB may use int as the Value), so using object or generics may not be possible.

That would only apply if you're extending the class. There's no problem if you wrap the class instead, i.e. create a MyObject<int> and access its Value property, rather than subclassing it.

Having said that, if you want to subclass a generic class, the subclass would also need to be a generic class.

R. Bemrose
A: 

I've written a similar class that could hold either a single instance of ClassX or an array of ClassX. The trick was that it could change during runtime, so a generic wouldn't suffice, but I still wanted it strong-typed in all cases. It sounds like that's similar to what you're trying to accomplish here.

I chose the first option, and here's why: Wherever possible, I encapsulate complexity within a class to make the class easier to use. Classes should encapsulate away complexity from the caller, making calls to it more concise. If using MyObjectB makes your code more verbose, than I don't think that's the right answer.

Mashmagar
Just for reference, why would ClassX not just always hold an array, which could be an array of 1 element. Seems like overkill (and abuse of OOP), but I don't know the specifics here.
drharris
@drharris: Because a single item is the common case and I didn't want to add a check on every function that expects a single item to make sure the array has exactly one element. With this solution, I can rely on the type system to catch any mistakes I make. No, it's not pretty, but it works well for what I needed. Since a single item is the common case, it would also be misleading to code maintainers to see an array everywhere.
Mashmagar
Thanks for the update. I had done something very similar a few years ago at a previous job and was told to make it work for the general case only (always array, regardless of length). Good to hear that someone has used such a pattern successfully.
drharris