Tengo un método que usa an IList<T>
como parámetro. Necesito verificar cuál es el tipo de ese T
objeto y hacer algo basado en él. Intentaba usar el T
valor, 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 T
que se pasa y luego usar una switch
declaración?
Respuestas:
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)){...}
fuente
switch-case
lugar deif-else
?Puede utilizar
typeof(T)
.private static string BuildClause<T>(IList<T> clause) { Type itemType = typeof(T); if(itemType == typeof(int) || itemType == typeof(decimal)) ... }
fuente
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í.
fuente
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") } } }
fuente
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"));
fuente
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.
fuente
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.
fuente
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.
fuente
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.
fuente
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
fuente
Qué tal esto :
// Checks to see if the value passed is valid. if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value)) { throw new ArgumentException(); }
fuente