2009-05-12 12 views
4

J'ai écrit une simple classe générique abstraite en C# (.NET 2.0) et je préfère de préférence la limiter aux seuls types de référence, donc je ne peux indiquer aucune valeur par un null. Cependant, je veux aussi utiliser des types tels que long et décimal, pourquoi ne pas autoriser null (étant structs après tout). J'ai considéré faire la classeQuels types utiliser pour la boxe dans les génériques

public abstract Field<Nullable<T>> 
{ 

} 

Mais cela empêcherait mon utilisation du type chaîne, qui est une classe. Comment puis-je boxer mes décimales et les longs afin que je puisse les utiliser dans ce générique.

abstract class Field<T> 
{ 
    private int _Length = 1; 
    private bool _Required = false; 
    protected T _Value; //= null; 

    public int Length 
    { 
     get { return _Length; } 
     private set 
     { 
      if (value < 1) throw new ArgumentException("Field length must be at least one."); 
      _Length = value; 
     } 
    } 

    public bool Required 
    { 
     get { return _Required; } 
     private set { _Required = value; } 
    } 

    public abstract string GetFieldValue(); 
    public abstract void ParseFieldValue(string s); 

    public virtual T Value 
    { 
     get { return _Value; } 
     set 
     { 
      if (value == null && Required) 
       throw new ArgumentException("Required values cannot be null."); 
      _Value = value; 
     } 
    } 

} 

Veuillez noter que j'ai besoin de représenter différemment les valeurs numériques 0 et nulles. Pour cette raison, la valeur par défaut (T) ne fonctionnera pas.

Répondre

2

Vous devez deux classes

abstract class FieldR<T> where T: class 
{ 
    T Value { get {} set {} } 
} 

abstract class FieldV<T> where T: struct 
{ 
    Nullable<T> Value { get {} set {} } 
} 

La première classe utiliserait

T 

Alors que la deuxième classe utiliserait

Nullable<T> 
+0

Cela peut effectivement fonctionner si les deux descendent d'un ancêtre commun non-générique (Champ) ... –

+0

@ C. Ross - Oui, cela peut très bien fonctionner. J'ai utilisé une classe de base non générique pour activer la branche de l'héritage avant. – stevehipwell

0

Essayez ceci:

public abstract Field<T> 
    where T : class 
{ 

} 

Cela limite le paramètre de type générique pour être un type de référence. C'est la seule façon que vous serez en mesure de retourner null de cette méthode. Oui, cela vous empêchera de passer des types de valeur à la méthode.

+0

Cela ne permettra pas les longs ou les décimaux, comme indiqué spécifiquement comme requis dans la question. S'il vous plaît lire avant de poster. –

+0

@C. Ross - J'ai lu avant de poster. Si vous relisez ma réponse, vous remarquerez que je signale que le seul moyen de renvoyer _null_ d'une méthode générique qui retourne une valeur qui est le type de l'un des arguments génériques est de contraindre cet argument avec "class". –

3

Le but des génériques (entre autres) est d'éviter la boxe. Voir this:

private bool _Required = false; 
protected T _Value = default(T); 

Si vous avez besoin de faire la distinction entre "0" et "non définie", object est votre seule issue:

protected object _Value; 

Et puis boîte-Unbox-box-Unbox.

+0

Une bonne réponse, mais malheureusement, je dois représenter 0 et "Aucune valeur" séparément. –

0

C'est une bonne question. Je souhaite que ce soit possible:

class MyClass<T> where T : struct { 
    T? value; 
    ... 
} 
class MyClass<T> where T : class { 
    T value; 
    ... 
} 

et le compilateur choisirais la classe générique correcte selon que les contraintes sont satisfaites. Malheureusement, cela ne fonctionne pas, ce qui peut causer des problèmes avec la génération automatique du code source.

1

Je ne sais pas si vous pouvez limiter les paramètres génériques de manière appropriée au moment de la compilation, mais vous pourriez ajouter quelques contrôles d'exécution pour permettre seulement la référence ty pes ainsi que le sous-ensemble souhaité des types de valeur nullables:

public virtual T Value 
{ 
    get { return _Value; } 
    set 
    { 
     Type t = typeof(T); 
     if (t.IsValueType) 
     { 
      if (t.IsGenericType 
       && (t.GetGenericTypeDefinition() == typeof(Nullable<>))) 
      { 
       Type u = Nullable.GetUnderlyingType(t); 
       if ((u != typeof(long)) && (u != typeof(decimal))) 
       { 
        throw new ArgumentException(
         "Only long? and decimal? permitted!"); 
       } 
      } 
      else 
      { 
       throw new ArgumentException("Only nullable types permitted!"); 
      } 
     } 

     if ((value == null) && Required) 
     { 
      throw new ArgumentException("Required values cannot be null!"); 
     } 

     _Value = value; 
    } 
} 

(. En réalité, vous auriez probablement envie de mettre vos chèques de type dans un constructeur plutôt que le poseur Value)

0

Je dirais que vous créez un titulaire de classe <T> avec un paramètre de type T non contraint, qui expose un champ de type T; il aurait à la fois un constructeur par défaut et un constructeur à un seul argument de type T.De plus, vous pourriez vouloir définir une interface IReadableHolder <T>, qui serait implémentée par Holder <T>, mais qui pourrait aussi être implémentée par n'importe quel type de classe que vous définissez (une instance de la classe Foo implémenterait IReadableHolder <Foo> en ayant sa propriété Value retourne elle-même, permettant ainsi à une routine d'attendre un IReadableHolder <Foo> d'accepter soit un titulaire <Foo> soit un foo lui-même Dommage qu'il n'y a pas moyen de définir une "interface d'extension" pour ajouter une implémentation de IHolder <String> à la classe String

Questions connexes