2012-01-05 26 views
28

Estoy tratando de escribir un método de extensión en IEnumerable que solo se aplicará a los tipos de valores y cadenas.C# Restricciones genéricas para incluir tipos de valores Y cadenas

public static string MyMethod<T>(this IEnumerable<T> source) where T : struct, string 

Sin embargo, 'cadena' no es una restricción válida, ya que es una clase sellada.

¿Hay alguna manera de hacer esto?

Editar:

Lo que realmente estoy tratando de hacer es preparar una lista de valores para una cláusula de "IN" en un SQL dinámicamente construido.

Tengo un montón de ejemplos de código como el siguiente que quiero limpiar:

sb.AppendLine(string.Format("AND value IN ({0})", string.Join(",", Values.Select(x => x.ToSQL()).ToArray()))); 

Dónde ToSQL() tiene código para manejar Inyección SQL.

+0

Para su implementación, ¿qué hace que los tipos de valor y las cadenas sean aceptables cuando otros no lo son? –

Respuesta

22

No, no puedes. Las restricciones genéricas siempre son "AND" -ed, si ve lo que quiero decir (es decir, todas las restricciones deben cumplirse), incluso si intentara usar alguna clase no sellada, esto aún fallaría.

¿Por qué quieres hacer esto? Tal vez haya otro enfoque que funcione mejor.

+0

Gracias. ¿Cuál sería la mejor alternativa? Dos métodos separados? –

+0

@Poz: dado que no formatearía valores en SQL para empezar, sugeriría refactorizar para usar consultas parametrizadas en su lugar ... –

+0

Inicialmente intentamos seguir esa ruta. Sin embargo, los problemas con la aprobación de listas como parámetros en SQL Server y la necesidad de dividir algo que podría ser un texto válido dentro de los valores nos hicieron cambiar nuestro enfoque. El SQL también se construye dinámicamente, con combinaciones condicionales, etc., que pensamos que sería mejor hacer en código en lugar de dentro de un procedimiento almacenado. Es una consulta que puede tener muchas permutaciones de parámetros arrojados, por lo que no podemos convertirlo en sql estático. –

32

Es necesario definir 2 métodos diferentes:

public static string MyMethod<T>(this IEnumerable<T> source) where T : struct 
public static string MyMethod(this IEnumerable<string> source) 
+1

También podría tener un tercer método privado que ambos métodos llaman para mantener las cosas un poco más SECAS. Vea [esta respuesta] (http://stackoverflow.com/a/4109547/957950) a una pregunta similar. – brichins

44

tal vez usted podría restringir a tipos IConvertible? Todas las primitivas del sistema que se pueden convertir usando estos métodos de interfaz también implementar la interfaz, por lo que esta restricción requeriría T a ser uno de los siguientes:

  • Boolean
  • Byte
  • Char
  • DateTime
  • decimal
  • doble
  • Int (16, 32 y 64 bits)
  • SByte
  • Individual (float)
  • cadena
  • UInt (16, 32 y 64 bits)

Si usted tiene una IConvertible, es muy probable que uno de estos tipos, como el La interfaz IConvertible es tan complicada de implementar que rara vez se realiza para tipos de terceros.

El inconveniente principal es que sin convertir realmente T a una instancia de uno de estos tipos, todo lo que su método sabrá hacer es llamar a los métodos Object e IConvertible, o métodos que toman un objeto o IConvertible. Si necesita algo más (como la capacidad de agregar y/o concatenar usando +), creo que simplemente establecer dos métodos, uno genérico para tipos de estructura y otro fuertemente tipado para cadenas, sería la mejor opción en general.

+1

¡Gran idea! No había pensado en eso. –

8

Utilicé una solución de hack: interfaz. Ver las interfaces de los tipos de valor integradas y tipo de cadena han implementado:

struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int> 

class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string> 

struct Boolean : IComparable, IConvertible, IComparable<bool>, IEquatable<bool> 

struct DateTime : IComparable, IFormattable, IConvertible, ISerializable, IComparable<DateTime>, IEquatable<DateTime> 

struct UInt64 : IComparable, IFormattable, IConvertible, IComparable<ulong>, IEquatable<ulong> 

struct Single : IComparable, IFormattable, IConvertible, IComparable<float>, IEquatable<float> 

struct Byte : IComparable, IFormattable, IConvertible, IComparable<byte>, IEquatable<byte> 

struct Char : IComparable, IConvertible, IComparable<char>, IEquatable<char> 

struct Decimal : IFormattable, IComparable, IConvertible, IComparable<decimal>, IEquatable<decimal> 

Usted puede utilizar IComparable,IConvertible,IEquatable<T> de limitaciones. De esta manera:

public static void SetValue<T>(T value) where T : IComparable, IConvertible, IEquatable<T> 
    { 
     //TODO: 
    } 

O puede utilizar código de tipo para comprobar el tiempo de datos sin restricciones.

public static void SetValue<T>(T value) 
    { 
     switch (Type.GetTypeCode(typeof(T))) 
     { 
      #region These types are not what u want, comment them to throw ArgumentOutOfRangeException 

      case TypeCode.Empty: 
       break; 
      case TypeCode.Object: 
       break; 
      case TypeCode.DBNull: 

       #endregion 

       break; 
      case TypeCode.Boolean: 
       break; 
      case TypeCode.Char: 
       break; 
      case TypeCode.SByte: 
       break; 
      case TypeCode.Byte: 
       break; 
      case TypeCode.Int16: 
       break; 
      case TypeCode.UInt16: 
       break; 
      case TypeCode.Int32: 
       break; 
      case TypeCode.UInt32: 
       break; 
      case TypeCode.Int64: 
       break; 
      case TypeCode.UInt64: 
       break; 
      case TypeCode.Single: 
       break; 
      case TypeCode.Double: 
       break; 
      case TypeCode.Decimal: 
       break; 
      case TypeCode.DateTime: 
       break; 
      case TypeCode.String: 
       break; 
      default: 
       throw new ArgumentOutOfRangeException(); 
     } 
    } 

Recuerde que no utilice el tipo de objeto sino el tipo genérico para el tipo de parámetro. De lo contrario, puede obtener una EXCEPCIÓN NULA en la línea de código Type.GetTypeCode(value.GetType()) cuando el valor es nulo.

0

Puede usar un constructor estático para verificar el parámetro de tipo cuando se usa la clase.

class Gen<T> { 
    static Gen() { 
     if (!typeof(T).IsValueType && typeof(T) != typeof(String)) 
     { 
      throw new ArgumentException("T must be a value type or System.String."); 
     } 
    } 
} 
+2

Esto no le ayuda en el momento de la compilación, que es lo que los genéricos realmente deberían utilizarse. También es muy grosero lanzar una excepción en un constructor, especialmente en un constructor estático: es muy probable que los consumidores obtengan una "TypeInitializerException" inútil en el tiempo de ejecución y no tengan idea de por qué. –

Cuestiones relacionadas