简体   繁体   中英

Generic method to type casting

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?

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.

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!");
            }
        }

    }
}

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.

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.

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.

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.

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)

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM