Lanzar una variable usando una variable Tipo

281

En C #, ¿puedo convertir una variable de tipo objeto en una variable de tipo T donde T se define en una variable Tipo?

theringostarrs
fuente
12
No es estrictamente sobre el tema, pero parece lo suficientemente confuso sobre lo que significa "transmitir", por lo que podría ser una buena idea comprender con precisión cuál es el propósito y la semántica del operador de transmisión. Aquí hay un buen comienzo: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert
2
Pensé que se me ocurrió algo. Si tiene una Typevariable, puede usar la reflexión para crear una instancia de ese tipo. Y luego puede usar un método genérico para devolver el tipo que desee deduciéndolo de un parámetro de ese tipo. Desafortunadamente, cualquier método de reflexión que cree una instancia de un tipo tendrá un tipo de retorno object, por lo que su CastByExamplemétodo genérico también lo usará object. Entonces, realmente no hay forma de hacer esto, e incluso si lo hubiera, ¿qué harías con el objeto recién lanzado? No podías usar sus métodos ni nada porque no conoces su tipo.
Kyle Delaney
@KyleDelaney ¡Gracias, estoy completamente de acuerdo! Como traté de explicar en mi respuesta, en realidad no es tan útil lanzar algo a una cosa diferente sin definir en algún momento el Tipo que realmente estás usando. El punto completo de los tipos es la verificación de tipo de tiempo del compilador. Si solo necesita hacer llamadas en el objeto, puede usar objecto dynamic. Si desea cargar dinámicamente módulos externos, puede hacer que las clases compartan una interfaz común y convertir el objeto en eso. Si no controla el código de terceros, cree pequeños contenedores e implemente la interfaz en eso.
Zyphrax

Respuestas:

203

Aquí hay un ejemplo de un elenco y un converso:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Editar:

Algunas personas en los comentarios dicen que esta respuesta no responde la pregunta. Pero la línea (T) Convert.ChangeType(input, typeof(T))proporciona la solución. El Convert.ChangeTypemétodo intenta convertir cualquier objeto al tipo proporcionado como segundo argumento.

Por ejemplo:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

He escrito la respuesta con los genéricos, porque creo que es un muy probable signo de olor código cuando se desea fundido a somethinga a something elsesin manipular un tipo real. Con interfaces adecuadas que no deberían ser necesarias el 99.9% de las veces. Quizás haya algunos casos extremos cuando se trata de reflexionar que podría tener sentido, pero recomendaría evitar esos casos.

Edición 2:

Pocos consejos adicionales:

  • Intenta mantener tu código lo más seguro posible. Si el compilador no conoce el tipo, entonces no puede verificar si su código es correcto y cosas como autocompletar no funcionarán. Simplemente dicho: si no puede predecir el tipo (s) en el momento de la compilación, ¿cómo podría hacerlo el compilador ?
  • Si las clases con las que está trabajando implementan una interfaz común , puede convertir el valor en esa interfaz. De lo contrario, considere crear su propia interfaz y haga que las clases implementen esa interfaz.
  • Si está trabajando con bibliotecas externas que está importando dinámicamente, busque también una interfaz común. De lo contrario, considere crear pequeñas clases de contenedor que implementen la interfaz.
  • Si desea realizar llamadas al objeto, pero no le importa el tipo, almacene el valor en una objecto dynamicvariable.
  • Los genéricos pueden ser una excelente manera de crear código reutilizable que se aplique a muchos tipos diferentes, sin tener que conocer los tipos exactos involucrados.
  • Si está atascado, considere un enfoque diferente o un refactorizador de código. ¿Tu código realmente tiene que ser tan dinámico? ¿Tiene que dar cuenta de algún tipo que haya?
Zyphrax
fuente
145
No sé cómo está ayudando a OP. Ella tiene una variable de tipo, no Tcomo tal.
nawfal
12
@nawfal, básicamente la línea Convert.ChangeType(input, typeof(T));da la solución. Puede reemplazar fácilmente typeof(T)con una variable de tipo existente. Una mejor solución (si es posible) sería evitar el tipo dinámico por completo.
Zyphrax
59
@Zyphrax, no, todavía requiere un elenco para el Tque no está disponible.
nawfal
44
Sé que el objeto resultante es realmente de tipo, Tpero aún así solo obtienes un objectcomo referencia. hmm, encontré la pregunta interesante en la premisa de que OP solo tiene la Typevariable y ninguna otra información. Como si la firma del método es Convert(object source, Type destination):) Sin embargo me sale ur punto
nawfal
10
¿Cómo es esto una solución a esta pregunta? Tengo el mismo problema y no tengo un <T> genérico. Solo tengo una variable de tipo.
Nuri Tasdemir
114

Otras respuestas no mencionan el tipo "dinámico". Por lo tanto, para agregar una respuesta más, puede usar el tipo "dinámico" para almacenar su objeto resultante sin tener que convertir el objeto convertido con un tipo estático.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Tenga en cuenta que con el uso de "dinámico" el compilador pasa por alto la comprobación de tipo estático que podría introducir posibles errores de tiempo de ejecución si no tiene cuidado.

maulik13
fuente
19
Esta es la respuesta correcta. Sin la palabra clave dinámica typeof (changedObj) es "objeto". Con la palabra clave dinámica, funciona perfectamente y typeof (changedObject) refleja correctamente el mismo tipo que typeVar. Además, no necesita (T) lanzar lo que no puede hacer si no conoce el tipo.
rushinge
55
Tengo la excepción "El objeto debe implementar IConvertible" al usar esta solución. ¿Alguna ayuda?
Nuri Tasdemir
@NuriTasdemir Es difícil de decir, pero creo que la conversión que está haciendo no es posible sin IConvertible. ¿Cuáles son los tipos involucrados en su conversión?
maulik13
Si bien esto funciona, hay una penalización de rendimiento con el uso de la dinámica. Recomendaría no usarlos a menos que esté trabajando con otros tiempos de ejecución (que es para lo que se diseñaron las dinámicas).
Bolo
19

Aquí está mi método para convertir un objeto pero no a una variable de tipo genérico, sino a una System.Typedinámica:

Creo una expresión lambda en tiempo de ejecución usando System.Linq.Expressions, de tipo Func<object, object>, que desempaqueta su entrada, realiza la conversión de tipo deseada y luego da el resultado encuadrado. Se necesita uno nuevo no solo para todos los tipos que se lanzan, sino también para los tipos que se lanzan (debido al paso de desempaquetado). La creación de estas expresiones requiere mucho tiempo, debido a la reflexión, la compilación y la construcción dinámica de métodos que se realiza bajo el capó. Afortunadamente, una vez creadas, las expresiones se pueden invocar repetidamente y sin sobrecarga, por lo que guardo en caché cada una.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Tenga en cuenta que esto no es mágico. La conversión no se produce en el código, como ocurre con la dynamicpalabra clave, solo se convierten los datos subyacentes del objeto. En el momento de la compilación, todavía tenemos que determinar minuciosamente exactamente qué tipo de objeto podría ser, lo que hace que esta solución sea poco práctica. Escribí esto como un truco para invocar operadores de conversión definidos por tipos arbitrarios, pero tal vez alguien por ahí pueda encontrar un mejor caso de uso.

balage
fuente
2
Requiereusing System.Linq.Expressions;
Aaron D
44
Para mí, esto tiene el mismo problema que la respuesta de Zyphrax. No puedo invocar métodos en el objeto devuelto porque todavía es del tipo "objeto". Ya sea que use su método ("a" a continuación) o su método ("b" a continuación) obtengo el mismo error en el molde (t) - "'t' es una variable pero se usa como un tipo.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
La respuesta original de @muusbolla Zyphrax utiliza genéricos y variables de tipo, no Type. No puedes lanzar usando la sintaxis de conversión normal si todo lo que tienes es el objeto Tipo. Si desea poder usar el objeto como algún tipo T en tiempo de compilación, no en tiempo de ejecución, debe convertirlo usando una variable de tipo o solo el nombre de tipo real. Puedes hacer lo primero usando la respuesta de Zaphrax.
Ashley
8

Dejando a un lado el boxeo y el desempaquetado por simplicidad, no hay una acción de tiempo de ejecución específica involucrada en el reparto a lo largo de la jerarquía de herencia. Es sobre todo una cuestión de tiempo de compilación. Esencialmente, un elenco le dice al compilador que trate el valor de la variable como otro tipo.

¿Qué podrías hacer después del elenco? No conoce el tipo, por lo que no podrá llamar a ningún método. No habría nada especial que pudieras hacer. Específicamente, puede ser útil solo si conoce los tipos posibles en el momento de la compilación, lo convierte manualmente y maneja cada caso por separado con ifdeclaraciones:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Mehrdad Afshari
fuente
1
¿Podría explicarlo más claramente en relación con mi pregunta?
theringostarrs
Lo que estoy tratando de explicar es, ¿qué podrías hacer después de eso? No se puede hacer tanto como el compilador de C # requiere tipos estáticos para ser capaz de hacer una cosa útil con el objeto
Mehrdad Afshari
Tienes razón. Sé los tipos esperados de dos variables que se envían al método como tipo 'objeto'. Quiero convertir a los tipos esperados almacenados en variables y agregarlos a la colección. Mucho más fácil ramificarse en tipo e intentar una conversión normal y detectar errores.
theringostarrs
44
Su respuesta es buena, pero solo para ser quisquilloso, noto que los yesos nunca afectan las variables . Nunca es legal emitir una variable a una variable de otro tipo; Los tipos de variables son invariantes en C #. Solo puede convertir el valor almacenado en la variable a otro tipo.
Eric Lippert
¿La introducción de la escritura dinámica de C # 4.0 cambia esta respuesta?
Daniel T.
6

¿Como pudiste? Necesita una variable o campo de tipo T donde pueda almacenar el objeto después del lanzamiento, pero ¿cómo puede tener esa variable o campo si conoce T solo en tiempo de ejecución? Entonces, no, no es posible.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?
Daniel Brückner
fuente
3
Si está utilizando una clase genérica, que define un método con valor de retorno de tipo T, es posible que deba hacerlo. Por ejemplo, analizar una cadena a una instancia de T y devolverla.
Oliver Friedrich
77
Esta no es la respuesta correcta afortunadamente. Ver la respuesta de maulik13.
rushinge
3
¿Dónde, en nombre del cielo, encuentras un CastTométodo Object?
ProfK
3

Cuando se trata de lanzar a Enum, escriba:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

Y lo llamarás así:

var enumValue = GetEnum(typeof(YourEnum), foo);

Esto fue esencial para mí en caso de obtener el valor del atributo Descripción de varios tipos de enumeración por valor int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

y entonces:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

Alternativamente (mejor enfoque), dicho casting podría verse así:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }
krzyski
fuente
1

Después de no encontrar nada para sortear la excepción "El objeto debe implementar IConvertible" al usar la respuesta de Zyphrax (excepto para implementar la interfaz) .. Intenté algo un poco poco convencional y trabajé para mi situación.

Usando el paquete Newtonsoft.Json nuget ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);
Brusco
fuente
1

Daño, el problema es que no tienes una T.

solo tiene una variable Tipo.

Sugerencia para MS, si pudieras hacer algo como

TryCast<typeof(MyClass)>

si resolvería todos nuestros problemas

usuario2825546
fuente
0

Nunca entenderé por qué necesita hasta 50 reputación para dejar un comentario, pero solo tenía que decir que la respuesta de @Curt es exactamente lo que estaba buscando y espero que alguien más.

En mi ejemplo, tengo un ActionFilterAttribute que estaba usando para actualizar los valores de un documento de parche json. No sabía cuál era el modelo T para el documento de parche, tuve que serializarlo y deserializarlo en un JsonPatchDocument simple, modificarlo, luego porque tuve el tipo, serializarlo y deserializarlo nuevamente al tipo.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );
Leye Eltee Taiwo
fuente
-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}
usuario2008563
fuente
2
¿Podría señalar cómo esta respuesta difiere de las otras respuestas y dónde es adecuada esta solución?
Klaus Gütter
-2

incluso más limpio:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }
Salomones Daños
fuente
-2

Si necesita lanzar objetos en tiempo de ejecución sin conocer el tipo de destino, puede usar la reflexión para hacer un convertidor dinámico.

Esta es una versión simplificada (sin el método generado de almacenamiento en caché):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

entonces puedes llamarlo:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
marianop
fuente