Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to design a multi-type object

Let's say I have a data object, but this object can hold one of several types of data.

class Foo
{
    int intFoo;
    double doubleFoo;
    string stringFoo;
}

Now, I want to create an accessor. Some way to get at this data. Obviously, I could create multiple accessors:

public int GetIntFoo();
public double GetDoubleFoo();
public string GetStringFoo();

Or I could create multiple properties

public int IntFoo { get; set; }
public double DoubleFoo { get; set; }
public string StringFoo { get; set; }

I don't that this is a very good design. It requires the client code to be more concerned about type than it should have to be. What's more, I really need only a single value for this class and the above would allow one of each type to be assigned at the same time. Not good.

One option is to use Generics.

class Foo<T>
{
    public T TheFoo { get; set; }
}

However, this doesn't create a Foo, it creates a Foo<T>. A different type for each, so I can't really use them as the same type.

I could derive Foo<T> from FooBase, then treat all of them as FooBase's, but then i'm back in the problem of accessing the data.

A different Generics option is to use something like this:

class Foo
{
    string stringRepresentationOfFoo;
    public T GetFoo<T>() { return /* code to convert string to type */ }
}

OF course the problem is that any kind of T could be passed, and frankly, it's a bit busy.

I could also just box the values and return an object, but then there is no type safety.

Ideally, I want to treat all Foo's the same, but I want type safety so that if there isn't a StringFoo, I can't even compile a reference to a StringFoo.

Foo foo = new Foo("Foo");
string sFoo = foo.Value;  // succeeds.
&nbsp;
Foo foo = new Foo(0);
int iFoo = foo.Value; // succeeds
string sFoo = foo.Value; // compile error

Perhaps this isn't even possible.. and I'll have to make some compromises, but maybe i'm missing something.

Any ideas?

EDIT:

Ok, so as daniel points out, the compile time checking of a runtime type is not practical.

What is my best option for doing what I want to do here? Namely, Treat all Foo's the same, but still have a relatively sane access mechanism?

EDIT2:

I don't want to convert the value to different types. I want to return the correct type for the value. That is, if it's a double, I don't want to return an int.

like image 614
Erik Funkenbusch Avatar asked Mar 01 '23 22:03

Erik Funkenbusch


2 Answers

How about passing in the variable as a parameter to the get? Like this:

int i = foo.get(i);

Then in your class, you'd have something like:

public int get(int p) {
    if(this.type != INTEGER) throw new RuntimeException("Data type mismatch");
    return this.intVal;
}

public float get(float p) {
    if(this.type != FLOAT) throw new RuntimeException("Data type mismatch");
    return this.floatVal;
}

This sort of turns the type checking inside-out: instead of checking what type foo holds, you have foo check what type you want. If it can give you that type, it does, or else it throws a runtime exception.

like image 133
TMN Avatar answered Mar 13 '23 02:03

TMN


I don't think this could work (giving you the compiler error you want)

What would you want this to do:

Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1);
int baz = bar.Value;

Is that a compiler error?

I think "treat them all the same" (at least the way you've described it) and "compile time error" are going to be mutually exclusive.

In any case, I think the "best way" is going to be a compromise between generics and inheritance. You can define a Foo<T> that is a subclass of Foo; then you can still have collections of Foo.

abstract public class Foo
{
  // Common implementation

  abstract public object ObjectValue { get; }
}

public class Foo<T> : Foo
{
   public Foo(T initialValue)
   {
      Value = initialValue;
   }

   public T Value { get; set; }

   public object ObjectValue
   { 
      get { return Value; }
   }
}
like image 27
Daniel LeCheminant Avatar answered Mar 13 '23 03:03

Daniel LeCheminant