Verificación de tipos y genéricos de C #

83

Tengo un método que usa an IList<T>como parámetro. Necesito verificar cuál es el tipo de ese Tobjeto y hacer algo basado en él. Intentaba usar el Tvalor, pero el compilador no lo permite. Mi solución es la siguiente:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

Tiene que haber una mejor manera de hacer esto. ¿Hay alguna forma de verificar el tipo de Tque se pasa y luego usar una switchdeclaración?

Jon
fuente
1
Personalmente, me gustaría saber qué está haciendo en especial para cada tipo de datos. Si está realizando aproximadamente la misma transformación para cada tipo de datos, podría ser más fácil asignar diferentes tipos a una interfaz común y operar en esa interfaz.
Juliet

Respuestas:

121

Podrías usar sobrecargas:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

O puede inspeccionar el tipo de parámetro genérico:

Type listType = typeof(T);
if(listType == typeof(int)){...}
Jonnii
fuente
23
+1: las sobrecargas son definitivamente la mejor solución aquí en términos de diseño y mantenibilidad a largo plazo. Una verificación de tipo en tiempo de ejecución de un parámetro genérico parece demasiado irónico para codificar con seriedad.
Juliet
Un gran ejemplo de cuándo esto sería útil es la serialización genérica con tipos muy variables. Si el objeto que se pasa es una cadena, ¿por qué hacer el trabajo adicional? Si es una cadena, simplemente devuelva la cadena original sin intentar ningún procesamiento adicional
watkinsmatthewp
Lo siento, ¿hay alguna manera de lograrlo usando en switch-caselugar de if-else?
Tân
@HappyCoding lamentablemente no = (es posible que pueda hacerlo con la próxima versión de C #.
jonnii
7
NO debe confiar en la sobrecarga genérica (consulte esta respuesta ) si sus sobrecargas son funcionalmente diferentes (considere también los efectos secundarios), porque no puede garantizar que se llamará a una sobrecarga más especializada. La regla aquí es la siguiente: si TIENE que hacer una lógica especializada para un tipo específico, debe verificar ese tipo y no usar la sobrecarga; sin embargo, si sólo PREFIERE hacer lógica especializada (es decir, para mejorar el rendimiento) pero todas las sobrecargas, incluido el caso general, dan como resultado el mismo resultado, entonces puede usar la sobrecarga en lugar de la verificación de tipos.
tomosius
23

Puede utilizar typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}
bdowden
fuente
7

Por defecto, sepa que no hay una gran manera. Hace un tiempo me frustré con esto y escribí una pequeña clase de utilidad que ayudó un poco e hizo la sintaxis un poco más limpia. Esencialmente convierte el código en

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

La publicación completa del blog y los detalles sobre la implementación están disponibles aquí.

JaredPar
fuente
6

Y, debido a que C # ha evolucionado, puede (ahora) usar la coincidencia de patrones .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

Y nuevamente con las expresiones de cambio en C # 8.0, la sintaxis se vuelve aún más concisa.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}
Equipo
fuente
4

El tipo de operador ...

typeof(T)

... no funcionará con la instrucción de cambio de c #. ¿Pero qué tal esto? La siguiente publicación contiene una clase estática ...

¿Existe una alternativa mejor que esta para 'encender el tipo'?

... que te permitirá escribir código como este:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
Robert Harvey
fuente
También vea la respuesta de JaredPar aquí.
Robert Harvey
3

Para todos los que dicen que verificar tipos y hacer algo basado en el tipo no es una gran idea para los genéricos, estoy de acuerdo, pero creo que podría haber algunas circunstancias en las que esto tenga perfectamente sentido.

Por ejemplo, si tiene una clase que dice que se implementa así (Nota: no estoy mostrando todo lo que hace este código por simplicidad y simplemente he cortado y pegado aquí, por lo que puede que no se compile o funcione como se esperaba como lo hace todo el código, pero transmite el punto. Además, Unit es una enumeración):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

Entonces, en resumen, creo que hay razones válidas por las que es posible que desee verificar de qué tipo es el genérico para hacer algo especial.

Juan
fuente
2

No hay forma de usar la instrucción switch para lo que desea que haga. La instrucción de cambio debe proporcionarse con tipos integrales, que no incluye tipos complejos como un objeto "Tipo" o cualquier otro tipo de objeto para el caso.

womp
fuente
2

Su construcción frustra completamente el propósito de un método genérico. Es feo a propósito porque debe haber una mejor manera de lograr lo que está tratando de lograr, aunque no nos ha dado suficiente información para averiguar qué es eso.

mqp
fuente
2

Puede hacerlo typeOf(T), pero comprobaría su método dos veces y me aseguraría de que no está violando la responsabilidad única aquí. Esto sería un olor a código, y eso no quiere decir que no deba hacerse, pero debe tener cuidado.

El objetivo de los genéricos es poder construir algoritmos de tipo agnóstico en los que no le importa cuál es el tipo o siempre que se ajuste a un determinado conjunto de criterios. Tu implementación no es muy genérica.

JoshBerke
fuente
2

Espero que encuentres esto útil:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

Jaider
fuente
0

Qué tal esto :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Bert
fuente