views:

271

answers:

7

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.

+3  A: 

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; }
   }
}
Daniel LeCheminant
True. I hadn't thought about runtime implications. Good point, thanks.
Mystere Man
@Mystere Man: You're going to have to cast it either which way; by definition something that is just a Foo doesn't know what its value type is; if it did, why would you have used the base class? (instead of the type=specific subclass)
Daniel LeCheminant
@Mystere Man: I suppose if you needed, you could have the base class return the value as an object.
Daniel LeCheminant
Yes, I mentioned that in my question. I don't like that option either.
Mystere Man
As I said in my question, what I want to do may not be possible. And if not, I accept that. I'm just trying to get other input here.
Mystere Man
@Mystere Man: It's certainly an interesting idea (I've tried to do similar sorts of things before)
Daniel LeCheminant
thank you for this hint! ++
Andreas Niedermair
A: 

One thing is to store any type in your internal state of the class, and another is to expose it externally. When you write a class, you are actually declaring a contract for its behavior. The way you write it will influence greatly how client code will look like when using the class.

For example, by implementing the IConvertible interface you state that your type can be converted to any CLR type as an equivalent value.

I have also seen implementations where a Value class was used to store results of calculations, results that could represent either a string, double, int or boolean. But, the problem was that client code had to check a Value.Type property of an enum {String, Integer, Double, Boolean} and then either cast the Value.Value property (which was exposed externally by the Value class as an Object type) or use the specific ValueString, ValueDouble, ValueInt, ValueBoolean getters.

Sergiu Damian
A: 

Why not just use string, double and int?


After info about collection: What about using object? You will have to check for types and such afterwards anyways. And to help you with that you can use the is and as operators. And the Enumerable.Cast Method, or even better, the Enumerable.OfType Method.

Svish
Because I'm trying to create an object that can be stored in a collection (actually used via an indexer) that represents all the possible types that are valid for the object.
Mystere Man
what about using object then?
Svish
As I said in my question, object was a possible solution, but not one I liked because of casting. Also, I don't want to cast or convert objects from one type to another, just return the type that the data is.
Mystere Man
Also, i'm not trying to return all the ints like OfType does. I want to return a specific element, and have it be of the type the data is, or rather have it return a value of that type.
Mystere Man
If you use OfType first, you can narrow it down to the value you want with First or whatever. Seems to me that you want your code to know what you are thinking. which of course is impossible...
Svish
A: 

Many systems use a helper methods to return the alternate types just as the .net frameworks base object has the ToString() method

Choose which is the best base type for each of your object and provide To methods for other cases

e.g.

class Foo{
    public Int32 Value { get; set; }
    public Byte ToByte() { return Convert.ToByte(Value); }
    public Double ToDouble() { return (Double)Value; }
    public new String ToString() { return Value.ToString("#,###"); }
}
TFD
The problem here is that i'm not trying to convert a value to other types, i'm trying to return the correct type for the value. if it's a double, i don't want to convert it to an int.
Mystere Man
A: 

Actually, what is the purpose of this class? The biggest problem seems to be design breaking at least SRP (single responsibility principle).

Nonetheless, if I'm reading it correctly, you'd like to store some value in the container, pass the container to client and type-safely retrieve the value.

With this approach, you can use your proposal, i.e.

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        Foo a = new Foo();
        a.SetValue(4);
        Console.WriteLine(a.GetValue<int>());

        Foo b = new Foo();
        a.SetValue("String");
        Console.WriteLine(a.GetValue<string>());

        Console.ReadLine();

        return 0;
    }
}


class Foo {
    private object value; // watch out for boxing here!
    public void SetValue(object value) {
        this.value = value;
    }

    public T GetValue<T>() {
        object val = this.value;
        if (val == null) { return default(T); } // or throw if you prefer
        try {
            return (T)val;
        }
        catch (Exception) {
            return default(T);
            // cast failed, return default(T) or throw
        }
    }
}
}

However, in that case why not simply pass data as object and cast by yourself?

Depending on your needs, you may also try "PHP in C#":

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        MyInt a = 1;
        MyInt b = "2";

        Console.WriteLine(a + b); // writes 3

        Console.ReadLine();

        return 0;
    }
}


class MyInt {
    private int value;

    public static implicit operator int(MyInt container) {
        return container.value;
    }

    public static implicit operator MyInt(int value) {
        MyInt myInt = new MyInt();
        myInt.value = value;
        return myInt ;
    }

    public static implicit operator MyInt(string stringedInt) {
        MyInt myInt = new MyInt();
        myInt.value = int.Parse(stringedInt);
        return myInt;
    }
}
}
No, I do not believe it to be a violation of the SRP. The data is used for the same purpose, so it's all a single responsibility. It's just available in different types and I don't wish to convert int data to doubles (with all the nastiness of IEEE to deal with) or strings due to performance.
Mystere Man
Your solutions are basically all already described in my question, and rejected for various reasons. I am likely going to have to re-examine them and make some compromises.
Mystere Man
Well, the question is what the consumer of the class really wants to do with the value. If you could provide any example of the code that uses the value...
+1  A: 

I'm sorry, I just don't buy your premise. If the data all have the same purpose, then they should all have the same type. Consider a class that's meant to hold the current temperature, as returned by one of several web services. All the services return the temperature in Centigrade. But one returns as an int, one returns as a double, and one returns it as a string.

It's not three different types - it's one type - double. You would simply need to convert the non-double returns into double, which is what the temperature is (or maybe float).

In general, if you have multiple representations of one thing, then it's still one thing, not multiple things. Convert the multiple representations into one.

John Saunders
Essentially, this is a field of a database (though it's not a database). There are 100 fields, and each field can hold one of 3 types of data, and there is no way to predict which is which (this comes from an external service I have no control over). I have to treat the values polymorphically
Mystere Man
so they are essentially "the same thing" even though they can be of different types. Would I design the "database" this way? No, but I don't have any choice in the matter.
Mystere Man
+1  A: 

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.

TMN
I think this may be the best solution in this case. I would rather use an out parameter.
Mystere Man