operator - dynamic type c#
Método genérico para escribir casting (8)
Estoy tratando de escribir método genérico para tipos de cast. Quiero escribir algo como Cast.To<Type>(variable)
lugar de (Type) variable
. Mi versión incorrecta de este método:
public class Cast
{
public static T To<T>(object o)
{
return (T) o;
}
}
Y esta es una prueba simple:
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);
Como ha adivinado, este código fallará con la InvalidCastException
.
¿Este código falla porque la máquina virtual no sabe cómo convertir la variable de tipo object
al tipo B
en tiempo de ejecución? Pero el mensaje de excepción dice: "no se puede convertir el objeto de tipo A al tipo B". Entonces, CLR sabe acerca del tipo real de variable o
, ¿por qué no puede realizar el casting?
Y aquí está la pregunta principal: ¿cómo debo reescribir el método T To<T>(object o)
para solucionar este problema?
De hecho, me he encontrado con este problema más de una vez y no me siento "sucio" cuando estoy limitado a los tipos que implementan la interfaz IConvertible
. ¡Entonces la solución se vuelve muy limpia!
private T To<T>(object o) where T : IConvertible
{
return (T)Convert.ChangeType(o, typeof(T));
}
He utilizado una variación de esto cuando, por ejemplo, escribí un tokenizador, donde la entrada era una cadena, pero donde los tokens podían interpretarse como cadenas, enteros y dobles.
Ya que está usando la clase Convert
, el compilador realmente tendrá información para saber qué hacer. No es solo un elenco simple.
Si necesita una forma de casting aún más genérica, me pregunto si esto no es más bien un problema de diseño en el código. Creo que un problema con la ampliación del alcance de estas cosas, es que cuantas más áreas intente cubrir, más difícil será para un extraño saber cuánto puede hacer el método.
Creo que es de suma importancia que el casting realmente funcione cuando alguien ha escrito específicamente un método para que el trabajo evite una situación como Add(x, y)
solo para ciertos valores de x
e y
.
Creo que la expectativa es diferente si lo intentas tú mismo, como en T1 x = (T1) T2 y
. Entonces creo que es más evidente que estás realmente solo, ya que acabas de hacer un lanzamiento en lugar de llamarlo "método de cobertura de todos los lanzamientos".
En este caso, está claro que se trata específicamente de objetos que implementan IConvertible
y el desarrollador puede asumir que funcionará bien con cualquiera de estos objetos.
Tal vez una respuesta pesada de filosofía orientada a objetos con la que no todos estarán de acuerdo, pero creo que este tipo de "preguntas conceptuales" a menudo terminan en la filosofía de programación.
La instancia a
es un objeto al momento de lanzar a B
No es A
tipo, sino un object
. Por lo tanto, es imposible convertir el object
en B
porque CLR no puede saberlo, o
contiene un operador explícito.
EDITAR:
¡Sí! Aquí está la solución:
public class Cast
{
public static T1 To<T1>(dynamic o)
{
return (T1) o;
}
}
Ahora CLR sabe exactamente que o
es una instancia de tipo A
y puede llamar al operador explícito.
Nunca logrará que esto funcione sin un ''convertidor de tipo'' (un proceso manual de mapeo de atributos para todos los tipos conocidos que simplemente no ocurrirá). Simplemente no puedes simplemente lanzar una clase concreta no relacionada a otra. Rompería el modelo de herencia única (que es uno de los principios definitorios de la POO moderna - lea sobre ''El problema del diamante'')
También se señaló acerca de las interfaces (polimorfismo): ambas clases también tendrían que derivar de la misma interfaz (que está en la misma línea)
Puedes hacer este truco encontrando los métodos correctos a través de la Reflexión:
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));
}
En .NET 4.0, puede usar palabras clave dynamic
como se sugiere en otras respuestas.
Si puedes usar c # 4.0 esto funciona:
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!");
}
}
}
}
Su Cast.To<T>()
solo está tratando de interpretar la referencia a un objeto dado como una referencia a T. Lo cual falla, por supuesto.
Y si el compilador encuentra (B) a
y sabe que a
es de tipo A
y tipo A
tiene A
operador de conversión de tiempo de compilación para escribir B
- emite esta conversión. No es tu caso.
Tal vez no es exactamente lo que debes hacer, pero eso funcionará:
public class Cast
{
public static targetType To<soureType, targetType>(object o)
{
return (targetType)((sourceType) o);
}
}
Pero bueno, tal método me parece inútil ...
Todo lo que se ha dicho sobre la resolución del operador es correcto ... pero esta es mi respuesta a su pregunta principal:
public static T To<T>(this object o)
{
return (T)(dynamic)o;
}
La clave aquí es que la conversión de o a dinámico obligará a .NET a buscar el operador explícito en tiempo de ejecución.
Además, ¿por qué no hacerlo un método de extensión?
En lugar de
A a = new A();
B b = Cast.To<B>(a);
tu puedes hacer
A a = new A();
B b = a.To<B>();
Un beneficio adicional de exponerlo como un método de extensión es que obtienes una interfaz fluida para el envío explícito (si te gusta ese tipo de cosas). Siempre he odiado la cantidad de equilibrio de paréntesis anidados que se requiere para la conversión explícita en .NET.
Así que puedes hacer:
a.To<B>().DoSomething().To<C>().DoSomethingElse()
en lugar de
((C)((B)a).DoSomething())).DoSomethingElse()
que, para mí, se ve más claro.