21

I'm trying to write generic method to cast types. I want write something like Cast.To<Type>(variable) instead of (Type) variable. My wrong version of this method:

public class Cast
{
    public static T To<T>(object o)
    {
        return (T) o;
    }
}

And this is simple test:

public class A
{
    public static explicit operator B(A a)
    {
        return new B();
    }
}

public class B
{
}

A a = new A();
B b = Cast.To<B>(a);

As you guessed, this code will fail with InvalidCastException.

Is this code fail because virtual machine doesn't know how to cast variable of type object to type B at run-time? But exception message says: "unable to cast object of type A to type B". So CLR knows about real type of variable o, why it cannot perform casting?

And here is main question: how should I rewrite method T To<T>(object o) to fix this problem?

  • I don't really like the idea of a "convert *anything* into a T". I guess I don't know enough about C#. – Tipx Jul 22 '11 at 20:22
  • 3
    And here is my main question: Why? In what way is `Cast.To(...)` easier to use than `(B)...`? – StriplingWarrior Jul 22 '11 at 20:26
  • @StriplingWarrior: JeffN825 explained - method makes fluent-interface-style calling possible. I can't see other reasons myself... – Ivan Danilov Jul 22 '11 at 20:48
  • @StriplingWarrior, please don't misunderstand the question, I'm not trying to replace traditional type casts, I want to understand how CLR works and how to solve described problem. –  Jul 23 '11 at 16:01
  • @shidzo: Ah, if it's just a matter of better understanding the CLR, I can understand that. – StriplingWarrior Jul 24 '11 at 02:46

8 Answers8

15

All of what's been said about the operator resolution is correct...but this is my answer to your main question:

    public static T To<T>(this object o)
    {
        return (T)(dynamic)o;
    }

The key here is that casting o to dynamic will force the .NET to search for the explicit operator at runtime.

Plus, why not make it an extension method?

Instead of

        A a = new A();
        B b = Cast.To<B>(a);

you can do

        A a = new A();
        B b = a.To<B>();

An added benefit of exposing it as an extension method is that you gain a fluent interface for explicit casting (if you like that sort of thing). I've always hated the amount of nested parenthesis balancing required for explicit casting in .NET.

So you can do:

a.To<B>().DoSomething().To<C>().DoSomethingElse() 

instead of

((C)((B)a).DoSomething())).DoSomethingElse()

which, to me, looks clearer.

Jeff
  • 35,755
  • 15
  • 108
  • 220
  • 2
    Nooo! Please do not introduce `dynamic` here! It is not overkill, it is OVERKILL :)) – Ivan Danilov Jul 22 '11 at 20:23
  • Extension method is a good idea if you're only going to do this with a handful of classes, but anymore than a few would make an awful lot of work. – CodingGorilla Jul 22 '11 at 20:23
  • I like the fact that having an extension method because it means you expose a fluent interface for explicit casting (I've always hated the amount of nested parenthesis balancing required for explicit casting in .NET. So you can do: a.To().DoSomething().To().DoSomethingElse() instead of ((C)((B)a).DoSomething())).DoSomethingElse – Jeff Jul 22 '11 at 20:26
  • 2
    Yep, I know about method extensions. To be honest, this problem with generic type casting was born exactly when I want more cleaner syntax to casts as you said above. I agree with you here and I also don't like tons of parenthesis and want to write something like `a.To()`. But method extensions are moot point and applying ones to `object` type isn't a good idea. –  Jul 23 '11 at 16:04
  • 1
    Sure, overusing extension methods applied to the object type isn't a good idea...but I'd say this one for casting is more than ok. – Jeff Jul 23 '11 at 20:18
  • Could you explain more about the use of the dynamic keyword here ? Ive looked around and just managed to find out that explicit casting requires an operator that does that... – Niklas Mar 17 '17 at 11:59
11

If you can use c# 4.0 this works:

namespace CastTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            A a = new A();
            B b = Cast.To<B>(a);
            b.Test();

            Console.Write("Done.");
            Console.ReadKey();
        }

        public class Cast
        {
            public static T To<T>(dynamic o)
            {
                return (T)o;
            }
        }

        public class A
        {
            public static explicit operator B(A a)
            {
                return new B();
            }
        }

        public class B
        {
            public void Test()
            {
                Console.WriteLine("It worked!");
            }
        }

    }
}
CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
3

You can do this trick by finding the right methods through Reflection:

public static T To<T> (object obj)
{
    Type sourceType = obj.GetType ();
    MethodInfo op = sourceType.GetMethods ()
                    .Where (m => m.ReturnType == typeof (T))
                    .Where (m => m.Name == "op_Implicit" || m.Name == "op_Explicit")
                    .FirstOrDefault();

    return (op != null)
        ? (T) op.Invoke (null, new [] { obj })
        : (T) Convert.ChangeType (obj, typeof (T));
}

In .NET 4.0, you can use dynamic keyword as suggested in other answers.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • 2
    Thanks for Reflection solution. But it's not truly complete. I have one more test for your: `double d = 1.5; int i = Cast.To(d);`. This will fail with InvalidCastException. –  Jul 23 '11 at 15:59
1

I've actually encountered this problem more than once and it doesn't feel as OOP "dirty" when I can limit myself to types implementing the IConvertible interface. Then the solution actually becomes very clean!

private T To<T>(object o) where T : IConvertible
{
    return (T)Convert.ChangeType(o, typeof(T));
}

I've used a variation of this when I for example wrote a tokenizer, where the input was a string, but where the tokens could be interpreted as both strings, integers, and doubles.

Since it's using the Convert class, the compiler will actually have information to know what to do. It's not just a simple cast.

If you need an even more generic way of casting, I have to wonder if this is not rather a design problem in code. I think a problem with broadening the scope for these things, is that the more areas you try to cover, the harder it will be for an outsider to know how much the method can do.

I think it's of utmost importance that the casting actually works out when someone has specifically written a method for the job to avoid a situation like Add(x, y) for only certain values of x and y.

I think the expectation is different if you try the casting yourself, as in T1 x = (T1) T2 y. Then I think it's more apparent that you're truly on your own, since you just made up some cast rather than called a "cover all casts method".

In this case, it's clear that it's specifically dealing with objects implementing IConvertible and the developer can assume it'll work well with any of these objects.

Maybe an object oriented-philosophy heavy answer that not everyone will even agree with, but I think these kinds of "conceptual questions" often end up in programming philosophy.

Jonas
  • 1,172
  • 1
  • 16
  • 26
1

Your Cast.To<T>() is just trying to interpret reference to given object as reference to T. Which fails of course.

And if compiler encounters (B) a and knows that a is of type A and type A has compile-time cast operator to type B - it emits this cast. It is not your case.

Ivan Danilov
  • 14,287
  • 6
  • 48
  • 66
  • Thanks for explanation about operator resolution at compile-time. You've answered my first question. –  Jul 23 '11 at 16:02
  • @shidzo: Here is the reference to C# spec (somewhat obsolete, but no significant changes there related to our discussion here were made since then): http://msdn.microsoft.com/en-us/library/aa691370%28v=vs.71%29.aspx – Ivan Danilov Jul 23 '11 at 16:09
1

Instance a is an object to the moment of casting to B. Not A type, but object. So, it is impossible to cast object to B because of CLR can not know, that o contains explicit operator.
EDIT:
Yeah! Here is solution:

public class Cast
{
    public static T1 To<T1>(dynamic o)
    {
        return (T1) o;
    }
}

Now CLR exactly knows, that o is an instance of type A and can call the explicit operator.

Sergey Metlov
  • 25,747
  • 28
  • 93
  • 153
  • 1
    Not correct. It is possible to cast `object` to `B`. If `object` variable contains instance of `B`. It is impossible to determine that it is required to call explicit conversion method to cast given `object` to `B` - that's right. – Ivan Danilov Jul 22 '11 at 20:04
  • 3
    @Ivan, you misunderstand @DotNET's point: it is that the explicit operator is registered against `A` and not `object`. – Kirk Woll Jul 22 '11 at 20:05
  • 2
    However, this still won't work. It will result in the compiler error, `Cannot convert type 'T2' to 'T1'`. This is because operator overload resolution happens at compile time and when the `Cast` class is compiled, `T1` is only `object` and so still fails to pick up the converter. – Kirk Woll Jul 22 '11 at 20:07
  • @Ivan is correct, DotNET is correct, he just slightly misstated it. – CodingGorilla Jul 22 '11 at 20:07
  • @Kirk Woll: It seems it is a game of words. By "cast" I mean getting value of type `B` from value of another type. And not calling cast operator. – Ivan Danilov Jul 22 '11 at 20:08
  • @Kirk is correct that it still wont work, because at compile time the compiler cannot guarantee that `T2` has the proper operator necessary to do the conversion. – CodingGorilla Jul 22 '11 at 20:13
  • Interestingly, even if you add the appropriate `where` clauses (`where T2 : A where T1 : B`) it *still* doesn't allow the cast. There must be some subtle rules about this sort of thing in the C# language spec. Eric or Jon, where are you? ;) – Kirk Woll Jul 22 '11 at 20:14
  • @Kirk I don't think the compiler does that much work to search the classes to see if they have compatible operators, seems like that would be a lot of work on the compiler's side. – CodingGorilla Jul 22 '11 at 20:17
  • @Kirk Woll: what if you have derivative of A that has different casting operators? Generic restrictions are just don't guaranteeing correct things. – Ivan Danilov Jul 22 '11 at 20:26
  • Yes, but you're explicitly saying to compiler that it should perform checks in runtime. And thus it is compiling. Without dynamic if it compiles - it just can't throw things. Except those your cast operator throws of course. – Ivan Danilov Jul 22 '11 at 20:50
1

You will never get this to work without a 'type converter'(a manual process of mapping across attributes for all known types which simply will not happen). You simply cannot just cast one non-related concrete class to another. It would break the single inheritance model (which is one of the defining principles of modern OOP - read up on 'the Diamond Problem')

It was also noted about interfaces (polymorphism) - both classes would have to derive from the same interface also (which is along the same lines)

Paul Sullivan
  • 2,865
  • 2
  • 19
  • 25
  • 2
    The diamond problem is only slightly touches these things. Here it is all about type system and type safety. Any of these types could be interfaces, btw. So what's then with single inheritance? :) – Ivan Danilov Jul 22 '11 at 20:15
  • What do you mean by `TypeConverter`? The OP *does* have a user type conversion operator registered. If you actually mean `System.ComponentModel.TypeConverter` that seems beside the point as this is language question, not an API question. – Kirk Woll Jul 22 '11 at 20:17
  • I think that `TypeConverter` was mentioned here because of principle rather than because of concrete implementation. You really should have such class if you need to convert classes from different parts of class hierarchy. So `TypeConverter` point seems correct to me. – Ivan Danilov Jul 22 '11 at 20:20
  • I have edited to include the phrase 'concrete class' Ivan and yes type converter is meant in the generic term of mapping attributes across (which would mean we would have to do so for all known and unknown types which is of course impossible). – Paul Sullivan Jul 23 '11 at 08:08
  • I'm afraid I don't understand how described problem and single inheritance model intersect here. But I think I've understood your thoughts about "type converter". I agree with your in this case, using `IConvertible` or `TypeConverter` or custom "type converter" won't be a bad idea. –  Jul 23 '11 at 16:03
  • @Paul: Weeell, I still think that Diamond problem has nothing to do here, but with your last edits I've taken my -1 away. – Ivan Danilov Jul 23 '11 at 16:13
0

Maybe not what you what to do exactly, but that will works:

public class Cast
{
    public static targetType To<soureType, targetType>(object o)
    {
        return (targetType)((sourceType) o);
    }
}

But well, such a method seems useless to me...

Johnny5
  • 6,664
  • 3
  • 45
  • 78
  • Wow. +1 just for working example. It seems it is a problem in this topic :) But you're right. It is slow and useless. – Ivan Danilov Jul 22 '11 at 20:22
  • Wow, stop. I missed the problem here :) It is the same as with first DotNET's attempt. Compiler doesn't know a way to cast sourceType to targetType. Aaaargh! Your +1 is locked now, I can't take it back. Lucky you, inattentive me)) – Ivan Danilov Jul 22 '11 at 20:30
  • It's not really the same thing, you have to provide the real type of the object (sourceType) and the type to cast to. That implies sourceType can be explicitely cast to target type. It is the same as `public static T1 To (T2 o) { return (T1) o; }` – Johnny5 Jul 23 '11 at 00:28
  • With my first exemple, you would do `A a = new A (); B b = Cast.To (a);`. It works, but it is useless. – Johnny5 Jul 23 '11 at 00:33
  • 1
    No, it can't. In compile-time for compiler these are just two generic types. Imagine A and B are placed in different assembly. And compiler don't know if conversion from `sourceType` to `targetType` exists because usages could be not written yet. And it tells (copy&paste from real compiler): "Cannot convert type 'sourceType' to 'targetType'". Period. – Ivan Danilov Jul 23 '11 at 00:54