Generic method to type casting
Solution 1
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!");
}
}
}
}
Solution 2
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.
Solution 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.
Solution 4
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.
Solution 5
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)
xzt
Updated on June 23, 2022Comments
-
xzt almost 2 years
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 typeB
at run-time? But exception message says: "unable to cast object of type A to type B". So CLR knows about real type of variableo
, why it cannot perform casting?And here is main question: how should I rewrite method
T To<T>(object o)
to fix this problem? -
Ivan Danilov almost 13 yearsNot correct. It is possible to cast
object
toB
. Ifobject
variable contains instance ofB
. It is impossible to determine that it is required to call explicit conversion method to cast givenobject
toB
- that's right. -
Kirk Woll almost 13 years@Ivan, you misunderstand @DotNET's point: it is that the explicit operator is registered against
A
and notobject
. -
Kirk Woll almost 13 yearsHowever, 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 theCast
class is compiled,T1
is onlyobject
and so still fails to pick up the converter. -
CodingGorilla almost 13 years@Ivan is correct, DotNET is correct, he just slightly misstated it.
-
Ivan Danilov almost 13 years@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. -
CodingGorilla almost 13 years@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. -
Kirk Woll almost 13 yearsInterestingly, 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? ;) -
Ivan Danilov almost 13 yearsThe 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? :)
-
Kirk Woll almost 13 yearsWhat do you mean by
TypeConverter
? The OP does have a user type conversion operator registered. If you actually meanSystem.ComponentModel.TypeConverter
that seems beside the point as this is language question, not an API question. -
CodingGorilla almost 13 years@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.
-
Ivan Danilov almost 13 yearsI 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. SoTypeConverter
point seems correct to me. -
Ivan Danilov almost 13 yearsWow. +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 almost 13 yearsNooo! Please do not introduce
dynamic
here! It is not overkill, it is OVERKILL :)) -
CodingGorilla almost 13 yearsExtension 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.
-
Ivan Danilov almost 13 years@Kirk Woll: what if you have derivative of A that has different casting operators? Generic restrictions are just don't guaranteeing correct things.
-
Jeff almost 13 yearsI 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<B>().DoSomething().To<C>().DoSomethingElse() instead of ((C)((B)a).DoSomething())).DoSomethingElse
-
Ivan Danilov almost 13 yearsWow, 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 almost 13 yearsYes, 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.
-
Johnny5 almost 13 yearsIt'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<T1, T2> (T2 o) { return (T1) o; }
-
Johnny5 almost 13 yearsWith my first exemple, you would do
A a = new A (); B b = Cast.To<A, B> (a);
. It works, but it is useless. -
Ivan Danilov almost 13 yearsNo, 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
totargetType
exists because usages could be not written yet. And it tells (copy&paste from real compiler): "Cannot convert type 'sourceType' to 'targetType'". Period. -
Paul Sullivan almost 13 yearsI 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).
-
xzt almost 13 yearsThanks for Reflection solution. But it's not truly complete. I have one more test for your:
double d = 1.5; int i = Cast.To<int>(d);
. This will fail with InvalidCastException. -
xzt almost 13 yearsThanks for explanation about operator resolution at compile-time. You've answered my first question.
-
xzt almost 13 yearsI'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
orTypeConverter
or custom "type converter" won't be a bad idea. -
xzt almost 13 yearsYep, 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<B>()
. But method extensions are moot point and applying ones toobject
type isn't a good idea. -
xzt almost 13 yearsThanks for solving main problem!
-
Ivan Danilov almost 13 years@shidzo: Here is the reference to C# spec (somewhat obsolete, but no significant changes there related to our discussion here were made since then): msdn.microsoft.com/en-us/library/aa691370%28v=vs.71%29.aspx
-
Ivan Danilov almost 13 years@Paul: Weeell, I still think that Diamond problem has nothing to do here, but with your last edits I've taken my -1 away.
-
Jeff almost 13 yearsSure, 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.
-
Aaron Anodide about 12 yearsthis is interesting - what's
dynamic
doing here? -
Niklas about 7 yearsCould 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...