web-dev-qa-db-fra.com

C # Contraintes génériques pour inclure des types de valeur ET des chaînes

J'essaie d'écrire une méthode d'extension sur IEnumerable qui ne s'appliquera qu'aux types de valeur et aux chaînes.

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

Cependant, 'chaîne' n'est pas une contrainte valide, car il s'agit d'une classe scellée.

Y a-t-il un moyen de faire ça?

Modifier:

Ce que j'essaie en réalité de faire est de préparer une liste de valeurs pour une clause "IN" dans un SQL construit dynamiquement.

Je souhaite nettoyer plusieurs exemples de code tels que les suivants:

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

Où ToSQL () a du code pour gérer SqlInjection.

36
Brett Postin

Non, tu ne peux pas. Les contraintes génériques sont toujours "ET", si vous voyez ce que je veux dire (c'est-à-dire tout les contraintes doivent être satisfaites), donc même si vous essayez d'utiliser une classe non scellée, cela échouera quand même.

Pourquoi veux-tu faire cela? Peut-être y a-t-il une autre approche qui fonctionnerait mieux.

27
Jon Skeet

Peut-être que vous pourriez restreindre aux types IConvertible? Toutes les primitives système pouvant être converties à l'aide de ces méthodes d'interface implémentent également l'interface. Cette restriction nécessiterait donc que T soit l'un des suivants:

  • Booléen 
  • Octet 
  • Carboniser 
  • Date et heure 
  • Décimal 
  • Double 
  • Int (16, 32 et 64 bits) 
  • SByte 
  • Simple (float) 
  • Chaîne 
  • UInt (16, 32 et 64 bits)

Si vous avez un IConvertible, les chances sont TRES bonnes que c'est l'un de ces types, car l'interface IConvertible est si pénible à mettre en œuvre que c'est rarement fait pour les types tiers.

L'inconvénient principal est que, sans convertir T en une instance de l'un de ces types, votre méthode saura tout simplement est d'appeler les méthodes Object et IConvertible, ou des méthodes qui prennent un objet ou IConvertible. Si vous avez besoin de quelque chose de plus (comme la possibilité d'ajouter et/ou de concaténer avec +), je pense que la mise en place de deux méthodes, l'une générique pour les types struct et l'autre pour les chaînes fortement typées serait le meilleur choix.

56
KeithS

Vous devez définir 2 méthodes distinctes:

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

J'ai utilisé une solution de hack: interface . Voir les interfaces implémentées par les types de valeur intégrés et le type de chaîne:

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>

Vous pouvez utiliser IComparable,IConvertible,IEquatable<T>for contraintes . Comme ceci:

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

Vous pouvez également utiliser le code de type pour vérifier l’heure des données sans contrainte.

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();
        }
    }

N'oubliez pas que n'utilisez pas le type d'objet mais le type générique pour le type de paramètre. Sinon, vous pourriez obtenir une EXCEPTION NULL au niveau de la codeline Type.GetTypeCode(value.GetType()) lorsque la valeur est null.

11
Sean C.

Vous pouvez utiliser un constructeur statique pour vérifier le paramètre type lorsque la classe est utilisée.

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.");
        }
    }
}
0
Tereza Tomcova