Question

J'ai une méthode générique définie comme ceci:

public void MyMethod<T>(T myArgument)

La première chose que je veux faire est de vérifier si la valeur de myArgument est la valeur par défaut pour ce type, à peu près comme ceci:

if (myArgument == default(T))

Mais cela ne compile pas car je n'ai pas garanti que T implémentera l'opérateur ==. J'ai donc changé le code en ceci:

if (myArgument.Equals(default(T)))

Maintenant, cela compile, mais échouera si myArgument est null, ce qui fait partie de ce que je teste. Je peux ajouter un contrôle nul explicite comme ceci:

if (myArgument == null || myArgument.Equals(default(T)))

Maintenant, cela me semble superflu. ReSharper suggère même que je change la partie myArgument == null en myArgument == default (T), qui est l'endroit où j'ai commencé. Existe-t-il un meilleur moyen de résoudre ce problème?

Je dois prendre en charge les deux types de référence et types de valeur.

Était-ce utile?

La solution

Pour éviter la boxe, le meilleur moyen de comparer les génériques pour l’égalité est avec EqualityComparer<T>.Default. Ceci respecte IEquatable<T> (sans boxe) ainsi que object.Equals, et gère tous les Nullable<T> & "; Levés &"; nuances. D'où:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

Cela correspondra à:

  • null pour les classes
  • null (vide) pour <=>
  • zero / false / etc pour les autres structures

Autres conseils

Que diriez-vous de cela:

if (object.Equals(myArgument, default(T)))
{
    //...
}

L'utilisation de la méthode static object.Equals() vous évite de procéder à la vérification null vous-même. Qualifier explicitement l'appel avec object. n'est probablement pas nécessaire en fonction de votre contexte, mais je préfixe normalement les static appels avec le nom du type uniquement pour rendre le code plus soluble.

J'ai pu localiser un article sur Microsoft Connect qui aborde ce problème en détail:

  

Malheureusement, ce problème est inhérent à sa conception et il n’existe pas de solution simple pour permettre l’utilisation de paramètres de type avec des types de valeur.

     

Si les types sont connus pour être des types de référence, la surcharge par défaut des variables de test d'objet définies pour l'égalité de référence, bien qu'un type puisse spécifier sa propre surcharge personnalisée. Le compilateur détermine la surcharge à utiliser en fonction du type statique de la variable (la détermination n'est pas polymorphe). Par conséquent, si vous modifiez votre exemple pour contraindre le paramètre de type générique T à un type de référence non scellé (tel que Exception), le compilateur peut déterminer la surcharge spécifique à utiliser et le code suivant sera compilé:

public class Test<T> where T : Exception
  

Si les types sont connus pour être des types valeur, effectue des tests d’égalité de valeur spécifiques en fonction des types exacts utilisés. Il n'y a pas de bon & Quot; défaut & Quot; comparaison ici puisque les comparaisons de références ne sont pas significatives sur les types de valeur et que le compilateur ne peut pas savoir quelle comparaison de valeur spécifique doit être émise. Le compilateur peut émettre un appel à ValueType.Equals (Object) mais cette méthode utilise la réflexion et est assez inefficace par rapport aux comparaisons de valeurs spécifiques. Par conséquent, même si vous deviez spécifier une contrainte de type valeur sur T, il n’ya rien de raisonnable que le compilateur génère ici:

public class Test<T> where T : struct
  

Dans le cas que vous avez présenté, où le compilateur ne sait même pas si T est un type valeur ou référence, il n’existe de la même manière aucune valeur à générer qui serait valable pour tous les types possibles. Une comparaison de référence ne serait pas valide pour les types de valeur et une sorte de comparaison de valeur serait inattendu pour des types de référence qui ne sont pas surchargés.

Voici ce que vous pouvez faire ...

J'ai vérifié que ces deux méthodes fonctionnent pour une comparaison générique des types référence et valeur:

object.Equals(param, default(T))

ou

EqualityComparer<T>.Default.Equals(param, default(T))

Faire des comparaisons avec le " == " opérateur, vous devrez utiliser l’une des méthodes suivantes:

Si tous les cas de T proviennent d'une classe de base connue, vous pouvez en informer le compilateur à l'aide de restrictions de type génériques.

public void MyMethod<T>(T myArgument) where T : MyBase

Le compilateur reconnaît alors comment effectuer des opérations sur MyBase et ne lève pas le & "opérateur" == 'ne pouvant pas être appliqué aux opérandes de type "T" et "T" & "; erreur que vous voyez maintenant.

Une autre option serait de limiter T à tout type qui implémente IComparable.

public void MyMethod<T>(T myArgument) where T : IComparable

Ensuite, utilisez la CompareTo méthode définie par la IComparable. interface .

Essayez ceci:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

qui devrait compiler et faire ce que vous voulez.

(modifié)

Marc Gravell a la meilleure réponse, mais je voulais poster un extrait de code simple que j’ai travaillé pour le démontrer. Il suffit de l’exécuter dans une simple application de console C #:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

Encore une chose: quelqu'un avec VS2008 peut-il essayer cela comme méthode d'extension? Je suis coincé avec 2005 ici et je suis curieux de voir si cela serait autorisé.

Modifier: Voici comment le faire fonctionner en tant que méthode d'extension:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

Pour gérer tous les types de T, y compris lorsque T est un type primitif, vous devez compiler les deux méthodes de comparaison:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

Il va y avoir un problème ici -

Si vous voulez que cela fonctionne pour tout type, default (T) sera toujours nul pour les types de référence et 0 (ou une structure remplie de 0) pour les types de valeur.

Ce n’est probablement pas le comportement que vous recherchez, cependant. Si vous souhaitez que cela fonctionne de manière générique, vous devrez probablement utiliser la réflexion pour vérifier le type de T et gérer des types de valeur différents des types de référence.

Alternativement, vous pouvez y appliquer une contrainte d'interface, qui peut également être un moyen de vérifier la valeur par défaut de la classe / struct.

Je pense que vous avez probablement besoin de scinder cette logique en deux parties et de vérifier d'abord la valeur null.

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

Dans la méthode IsNull, nous nous basons sur le fait que les objets ValueType ne peuvent pas être null par définition. Par conséquent, s'il est vrai que value est une classe dérivée de ValueType, nous savons déjà que ce n'est pas null. D'un autre côté, s'il ne s'agit pas d'un type de valeur, nous pouvons simplement comparer la valeur convertie à un objet avec la valeur null. Nous pourrions éviter la vérification avec ValueType en allant directement au transtypage en objet, mais cela signifierait qu'un type de valeur serait mis en boîte, ce que nous souhaitons probablement éviter, car cela implique qu'un nouvel objet est créé sur le tas.

Dans la méthode IsNullOrEmpty, nous recherchons le cas particulier d'une chaîne. Pour tous les autres types, nous comparons la valeur (qui sait déjà et non null) à sa valeur par défaut, qui pour tous les types de référence est null et pour les types de valeur une forme de zéro (si font partie intégrante).

À l'aide de ces méthodes, le code suivant se comporte comme prévu:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

j'utilise:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

Je ne sais pas si cela fonctionne avec vos besoins ou non, mais vous pouvez contraindre T à être un Type qui implémente une interface telle que IComparable, puis à utiliser la méthode ComparesTo () à partir de cette interface (que IIRC prend en charge / gère les valeurs null ) comme ceci:

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

Il existe probablement d'autres interfaces que vous pourriez également utiliser: IEquitable, etc.

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

L'opérateur '==' ne peut pas être appliqué aux opérandes de type 'T' et 'T'

Je ne vois pas comment y parvenir sans le test null explicite suivi de l'appel de la méthode ou de l'objet Equals.Equals comme suggéré ci-dessus.

Vous pouvez concevoir une solution à l'aide de System.Comparison, mais cela se traduira par davantage de lignes de code et une complexité accrue.

Je pense que vous étiez proches.

if (myArgument.Equals(default(T)))

Maintenant, cela compile, mais échouera si myArgument est nul, ce qui fait partie de ce que je teste. Je peux ajouter un contrôle nul explicite comme ceci:

Il vous suffit d'inverser l'objet sur lequel l'équivalent est appelé pour une élégante approche null-safe.

default(T).Equals(myArgument);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top