Question

Consider this chunk of code where I'm testing an unknown variable that could be an int, MyObj, or Tuple, and I'm doing some type checking to see what it is so that I can go on and handle the data differently depending on what it is:

class MyObj { }

// ...

void MyMethod(object data) {

    if (data is int) Console.Write("Datatype = int");
    else if (data is MyObj) Console.Write("Datatype = MyObj");
    else if (data is Tuple<object,object>) {

        var myTuple = data as Tuple<object,object>;
        if (myTuple.Item1 is int && myTuple.Item2 is int) Console.WriteLine("Datatype = Tuple<int,int>");
        else if (myTuple.Item1 is int && myTuple.Item2 is MyObj) Console.WriteLine("Datatype = Tuple<int,MyObj>");

        // other type checks            
    }
}

// ...

MyMethod(1);                            // Works : Datatype = int
MyMethod(new MyObj());                  // Works : Datatype = MyObj
MyMethod(Tuple.Create(1, 2));           // Fails
MyMethod(Tuple.Create(1, new MyObj());  // Fails

// and also...

var items = new List<object>() {
    1,
    new MyObj(),
    Tuple.Create(1, 2),
    Tuple.Create(1, new MyObj())
};
foreach (var o in items) MyMethod(o);

My problem is that a Tuple<int,MyObj> doesn't cast to Tuple<object,object>, yet individually, I can cast int to object and MyObj to object.

How do I do the cast?

Was it helpful?

Solution

If you want to check for any pair type, you'll have to use reflection:

Type t = data.GetType();
if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Tuple<,>))
{
    var types = t.GetGenericArguments();
    Console.WriteLine("Datatype = Tuple<{0}, {1}>", types[0].Name, types[1].Name)
}

You may be able to use overloading and dynamic instead of manually inspecting the type:

MyMethod(MyObject obj) { ... }
MyMethod(int i) { ... }
MyMethod(Tuple<int, int> t) { ... }
MyMethod(Tuple<int, MyObject> t) { ... }

foreach(dynamic d in items)
{
    MyMethod(d);
}

this will choose the best overload at runtime so you have access the tuple types directly.

OTHER TIPS

As you can see here, Tuple<T1, T2> is not covariant.

Rather than trying to make one method that can take any type of parameter, why not overload your function to recieve valid types you actually expect.

perhaps,

void MyMethod<T1,T2>(Tuple<T1, T2> data)
{
    // In case ToString() is overridden
    Console.WriteLine("Datatype = Tuple<{0}, {1}>",
        typeof(T1).Name,
        typeof(T2).Name);
}

void MyMethod(MyObj data)
{
    Console.WriteLine("Datatype = MyObj");
}

void MyMethod(int data)
{
    Console.WriteLine("Datatype = System.Int32");
}

This way no type checking is required, the compiler does it for you at compile time. This is not javascript, strong typing can be a benefit.

In your code you are explicitly checking against int and MyObj, as well as against their position (Item1 or Item2) as seen here:

    if (myTuple.Item1 is int && myTuple.Item2 is int)
    else if (myTuple.Item1 is int && myTuple.Item2 is MyObj)

You can do the same explicit check using the same framework you already wrote:

    if (data is int) Console.WriteLine("Datatype = int");
    else if (data is MyObj) Console.WriteLine("Datatype = MyObj");
    else if (data is Tuple<int, int>) Console.WriteLine("Datatype = Tuple<int,int>");
    else if (data is Tuple<int, MyObj>) Console.WriteLine("Datatype = Tuple<int,MyObj>");

The downsides to the above code are the same as what you were attempting to do with Tuple<object, object>: You have to explicitly check all combinations of what could go into the tuple as well as their positions, i.e., my code will not work as written if you pass in Tuple<double, double> or Tuple<MyObj, int>, but neither would your code if it actually worked as you wanted it to. If you don't want explicit/hardcoded values then it appears you have to use reflection as seen in Lee's answer, otherwise the above code does what you were intending with less work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top