64 votes

Contraintes génériques, où T: struct et où T: classe

Je tiens à différencier les cas suivants:

  1. Un simple type de valeur (par exemple, int)
  2. Nullable type de valeur (par exemple, int?)
  3. Un type de référence (par exemple, string) - en option, je ne voudrais pas de soins si ce mappée (1) ou (2) ci-dessus

J'ai le code suivant qui fonctionne très bien pour les cas (1) et (2):

static void Foo<T>(T a) where T : struct { } // 1

static void Foo<T>(T? a) where T : struct { } // 2

Cependant, si j'essaie de détecter les cas (3), comme cela, il ne compile pas:

static void Foo<T>(T a) where T : class { } // 3

Le message d'erreur est du Type 'X' définit déjà un membre appelé 'Foo' avec les mêmes types de paramètres. Eh bien, de toute façon je ne peut pas faire la différence entre where T : struct et where T : class.

Si je retire la troisième fonction (3), le code suivant ne compile pas, soit:

int x = 1;
int? y = 2;
string z = "a";

Foo (x); // OK, calls (1)
Foo (y); // OK, calls (2)
Foo (z); // error: the type 'string' must be a non-nullable value type ...

Comment puis-je obtenir de l' Foo(z) de compiler, de la cartographie à l'une des fonctions ci-dessus (ou à un tiers avec une autre contrainte, que je n'ai pas pensé)?

22voto

Lasse V. Karlsen Points 148037

Malheureusement, vous ne pouvez pas différencier le type de méthode à appeler uniquement en fonction des contraintes.

Vous devez donc définir une méthode dans une classe différente ou avec un nom différent à la place.

10voto

LukeH Points 110965

Suite à votre commentaire sur Marnix de réponse, vous pouvez obtenir ce que vous désirez à l'aide d'un peu de réflexion.

Dans l'exemple ci-dessous, sans contrainte Foo<T> méthode utilise la réflexion pour la ferme des appels appropriée à la contrainte de la méthode, soit FooWithStruct<T> ou FooWithClass<T>. Pour des raisons de performances, nous allons créer et de mettre en cache un typage fort délégué plutôt que d'utiliser de la plaine de la réflexion à chaque fois que l' Foo<T> méthode est appelée.

int x = 42;
MyClass.Foo(x);    // displays "Non-Nullable Struct"

int? y = 123;
MyClass.Foo(y);    // displays "Nullable Struct"

string z = "Test";
MyClass.Foo(z);    // displays "Class"

// ...

public static class MyClass
{
    public static void Foo<T>(T? a) where T : struct
    {
        Console.WriteLine("Nullable Struct");
    }

    public static void Foo<T>(T a)
    {
        Type t = typeof(T);

        Delegate action;
        if (!FooDelegateCache.TryGetValue(t, out action))
        {
            MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
            action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
            FooDelegateCache.Add(t, action);
        }
        ((Action<T>)action)(a);
    }

    private static void FooWithStruct<T>(T a) where T : struct
    {
        Console.WriteLine("Non-Nullable Struct");
    }

    private static void FooWithClass<T>(T a) where T : class
    {
        Console.WriteLine("Class");
    }

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}

(Notez que cet exemple n'est pas thread-safe. Si vous avez besoin de fil de sécurité, alors vous aurez besoin d'utiliser une sorte de verrouillage autour de tous les accès à la mémoire cache du dictionnaire, ou, si vous êtes en mesure de cibler .NET4 -- utilisation d' ConcurrentDictionary<K,V> à la place.)

5voto

Marnix van Valen Points 6197

Déposez la structure contraint sur la première méthode. Si vous devez différencier les types de valeur et les classes, vous pouvez utiliser le type de l'argument pour ce faire.

       static void Foo( T? a ) where T : struct
      {
         // nullable stuff here
      }

      static void Foo( T a )
      {
         if( a is ValueType )
         {
            // ValueType stuff here
         }
         else
         {
            // class stuff
         }
      }
 

2voto

supercat Points 25534

Amplifier mon commentaire à LukeH, un pattern très utile si l'on devra utiliser la Réflexion pour invoquer des actions différentes en fonction d'un paramètre de type (autre que le type d'une instance de l'objet) est de créer un privé générique de classe statique quelque chose comme ce qui suit (ce code n'est pas testé, mais j'ai fait ce genre de chose avant):

statique de la classe FooInvoker<T>
{
 l'Action publique<Foo> theAction = configureAction;
 void ActionForOneKindOfThing<TT>(TT param) où TT:thatKindOfThing,T
{
...
}
 void ActionForAnotherKindOfThing<TT>(TT param) où TT:thatOtherKindOfThing,T
{
...
}
 void configureAction(T param)
{
 ... Déterminer le genre de chose T, et la valeur " theAction à l'un des
 ... méthodes ci-dessus. Puis la fin avec ...
theAction(param);
}
}

Notez que la Réflexion va lever une exception si l'on tente de créer un délégué ActionForOneKindOfThing<TT>(TT param) lorsque TT n'est pas conforme à la méthode de contraintes. Parce que le système a validé le type d' TT lorsque le délégué a été créé, on peut en toute sécurité invoquer theAction sans plus de vérification de type. Notez également que si l'extérieur de code:

FooInvoker<T>.theAction(param);

seul le premier appel nécessitent de toute Réflexion. Les appels suivants seront simplement invoquer le délégué directement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X