web-dev-qa-db-fra.com

Contrainte de type générique C # pour tout nullable

Donc j'ai cette classe:

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

Maintenant, je cherche une contrainte de type qui me permette d’utiliser tout ce qui peut être un paramètre de type pouvant être null. Cela signifie que tous les types de référence, ainsi que tous les Nullable (T?) les types:

Foo<String> ... = ...
Foo<int?> ... = ...

devrait être possible.

L'utilisation de class comme contrainte de type ne me permet d'utiliser que les types de référence.

Informations complémentaires: J'écris une application de filtres et de filtres et souhaite utiliser une référence null comme dernier élément transféré dans le pipeline, de sorte que chaque filtre puisse se fermer correctement. faire le nettoyage, etc ...

92
user2963836

Si vous souhaitez effectuer une vérification à l'exécution dans le constructeur de Foo plutôt que d'avoir une vérification à la compilation, vous pouvez vérifier si le type n'est pas un type référence ou nullable et, le cas échéant, émettre une exception.

Je me rends compte que seulement avoir une vérification à l'exécution peut être inacceptable, mais juste au cas où:

public class Foo<T>
{
    private T item;

    public Foo()
    {
        var type = typeof(T);

        if (Nullable.GetUnderlyingType(type) != null)
            return;

        if (type.IsClass)
            return;

        throw new InvalidOperationException("Type is not nullable or reference type.");
    }

    public bool IsNull()
    {
        return item == null;
    }
}

Ensuite, le code suivant est compilé, mais le dernier (foo3) lève une exception dans le constructeur:

var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());

var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());

var foo3= new Foo<int>();  // THROWS
Console.WriteLine(foo3.IsNull());
21
Matthew Watson

Je ne sais pas comment implémenter l'équivalent de [~ # ~] ou [~ # ~] dans les génériques. Cependant, je peux proposer d'utiliser valeur par défaut clé Word afin de créer null pour les types nullable et 0 valeur pour les structures:

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

Vous pouvez également implémenter votre version de Nullable:

class MyNullable<T> where T : struct
{
    public T Value { get; set; }

    public static implicit operator T(MyNullable<T> value)
    {
        return value != null ? value.Value : default(T);
    }

    public static implicit operator MyNullable<T>(T value)
    {
        return new MyNullable<T> { Value = value };
    }
}

class Foo<T> where T : class
{
    public T Item { get; set; }

    public bool IsNull()
    {
        return Item == null;
    }
}

Exemple:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
        Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
        Console.WriteLine(new Foo<object>().IsNull()); // true
        Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false

        var foo5 = new Foo<MyNullable<int>>();
        int integer = foo5.Item;
        Console.WriteLine(integer); // 0

        var foo6 = new Foo<MyNullable<double>>();
        double real = foo6.Item;
        Console.WriteLine(real); // 0

        var foo7 = new Foo<MyNullable<double>>();
        foo7.Item = null;
        Console.WriteLine(foo7.Item); // 0
        Console.WriteLine(foo7.IsNull()); // true
        foo7.Item = 3.5;
        Console.WriteLine(foo7.Item); // 3.5
        Console.WriteLine(foo7.IsNull()); // false

        // var foo5 = new Foo<int>(); // Not compile
    }
}
17
Ryszard Dżegan

J'ai rencontré cette question pour un cas plus simple de vouloir une méthode statique générique pouvant prendre n'importe quoi "nullable" (types de référence ou Nullables), ce qui m'a amené à cette question sans solution satisfaisante. J'ai donc proposé ma propre solution, qui était relativement plus facile à résoudre que la question énoncée par le PO, en ayant simplement deux méthodes surchargées, une qui prend un T et a la contrainte where T : class Et une autre qui prend a T? et a where T : struct.

J'ai alors réalisé que cette solution pouvait également être appliquée à ce problème pour créer une solution vérifiable au moment de la compilation en rendant le constructeur privé (ou protégé) et en utilisant une méthode de fabrique statique:

    //this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000)
    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return Foo<TFoo>.Create(value);
        }

        public static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return Foo<TFoo?>.Create(value);
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo(T value)
        {
            item = value;
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return new Foo<TFoo>(value);
        }

        internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return new Foo<TFoo?>(value);
        }
    }

Maintenant, nous pouvons l'utiliser comme ceci:

        var foo1 = new Foo<int>(1); //does not compile
        var foo2 = Foo.Create(2); //does not compile
        var foo3 = Foo.Create(""); //compiles
        var foo4 = Foo.Create(new object()); //compiles
        var foo5 = Foo.Create((int?)5); //compiles

Si vous voulez un constructeur sans paramètre, vous n'obtiendrez pas le souci de surcharger, mais vous pouvez quand même faire quelque chose comme ça:

    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return Foo<TFoo>.Create<TFoo>();
        }

        public static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return Foo<TFoo?>.CreateNullable<TFoo>();
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo()
        {
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return new Foo<TFoo>();
        }

        internal static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return new Foo<TFoo?>();
        }
    }

Et utilisez-le comme ceci:

        var foo1 = new Foo<int>(); //does not compile
        var foo2 = Foo.Create<int>(); //does not compile
        var foo3 = Foo.Create<string>(); //compiles
        var foo4 = Foo.Create<object>(); //compiles
        var foo5 = Foo.CreateNullable<int>(); //compiles

Cette solution présente quelques inconvénients, notamment que vous préférez peut-être utiliser "nouveau" pour construire des objets. Une autre solution est que vous ne pourrez pas utiliser Foo<T> Comme argument de type générique pour une contrainte de type de type: where TFoo: new(). Enfin, voici le peu de code supplémentaire dont vous avez besoin ici et qui augmenterait surtout si vous avez besoin de plusieurs constructeurs surchargés.

11
Dave M

Comme mentionné, vous ne pouvez pas avoir de vérification à la compilation. Les contraintes génériques dans .NET font cruellement défaut et ne prennent pas en charge la plupart des scénarios.

Cependant, j'estime qu'il s'agit d'une meilleure solution pour la vérification à l'exécution. Il peut être optimisé au moment de la compilation JIT, car ce sont deux constantes.

public class SomeClass<T>
{
    public SomeClass()
    {
        // JIT-compile time check, so it doesn't even have to evaluate.
        if (default(T) != null)
            throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");

        T variable;
        // This still won't compile
        // variable = null;
        // but because you know it's a nullable type, this works just fine
        variable = default(T);
    }
}
7
Aidiakapi

J'utilise

public class Foo<T> where T: struct
{
    private T? item;
}
3
ela

Une telle contrainte de type n'est pas possible. Selon documentation des contraintes de type , il n'y a pas de contrainte qui capture à la fois les types nullable et référence. Comme les contraintes ne peuvent être combinées que dans une conjonction, il est impossible de créer une telle contrainte par combinaison.

Cependant, vous pouvez, pour vos besoins, revenir à un paramètre de type d'annulation de contrainte, car vous pouvez toujours rechercher == null. Si le type est un type valeur, la vérification sera toujours évaluée à false. Ensuite, vous obtiendrez éventuellement l’avertissement R # "Comparaison possible du type de valeur avec null", ce qui n’est pas critique, tant que la sémantique vous convient.

Une alternative pourrait être d'utiliser

object.Equals(value, default(T))

au lieu de la vérification null, puisque default (T) où T: class est toujours null. Cela signifie toutefois que vous ne pouvez pas distinguer une valeur non nullable qui n'a jamais été définie explicitement ou simplement définie à sa valeur par défaut.

3
Sven Amann