Marquer les paramètres comme NON Nullables dans C # /. NET?

99

Existe-t-il un attribut simple ou un contrat de données que je peux attribuer à un paramètre de fonction qui empêche nulld'être passé en C # /. NET? Idéalement, cela vérifierait également au moment de la compilation pour s'assurer que le littéral nulln'est utilisé nulle part pour lui et au moment de l'exécution ArgumentNullException.

Actuellement, j'écris quelque chose comme ...

if (null == arg)
  throw new ArgumentNullException("arg");

... pour chaque argument que j'espère ne pas être null.

Sur la même note, y a-t-il un contraire à celui Nullable<>par lequel ce qui suit échouerait:

NonNullable<string> s = null; // throw some kind of exception
Neil C. Obremski
la source
7
Avec les versions récentes de C #, l'utilisation de "nameof ()" fonctionne mieux: throw new ArgumentNullException (nameof (arg)); De cette façon, si vous refactorisez le nom, s'il se répercute dans votre déclaration de lancer
Flydog57
C # 8 prend désormais en charge les types de référence Nullable, une option du compilateur que vous pouvez activer dans votre projet. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Paul Stegler

Réponses:

69

Il n'y a malheureusement rien de disponible au moment de la compilation.

J'ai un peu une solution hacky que j'ai publiée sur mon blog récemment, qui utilise une nouvelle structure et des conversions.

Dans .NET 4.0 avec le truc des contrats de code , la vie sera beaucoup plus agréable. Il serait toujours très agréable d'avoir une syntaxe de langage réelle et un support autour de la non-nullabilité, mais les contrats de code aideront beaucoup.

J'ai également une méthode d'extension dans MiscUtil appelée ThrowIfNull qui le rend un peu plus simple.

Un dernier point - une raison d'utiliser " if (null == arg)" au lieu de " if (arg == null)"? Je trouve ce dernier plus facile à lire, et le problème que le premier résout en C ne s'applique pas à C #.

Jon Skeet
la source
2
> Il n'y a malheureusement rien de disponible au moment de la compilation. > ...> Ce serait toujours très agréable d'avoir une syntaxe de langage réelle et un support autour de la non-nullabilité, je suis d'accord. Je voudrais également que des erreurs de compilation soient soulevées.
AndrewJacksonZA
2
@Jon, "if (arg = null)" ne fonctionne-t-il pas s'il se trouve qu'un cast implicite en booléen est défini? J'avoue que cela peut sembler pervers, mais cela compile ...
Thomas S.Trias
11
@ ThomasS.Trias: Oui, dans ce cas de bord incroyablement obscur, combiné à une faute de frappe, combiné à un manque de tests autour de ce code, vous vous retrouveriez avec un problème. À ce stade, je pense que vous avez de plus gros problèmes :)
Jon Skeet
4
@Jon: d'accord. Je suppose que je vais abandonner mes conditions de Yoda bien-aimées. :-)
Thomas S. Trias
1
@SebastianMach: Je ne pense pas que la raison if (null == x)soit due aux différences de langage naturel. Je crois qu'il s'agit vraiment d'un style obsolète qui se propage simplement à travers des exemples, etc.
Jon Skeet
22

Je sais que je suis incroyablement en retard pour cette question, mais je pense que la réponse deviendra pertinente à mesure que la dernière itération majeure de C # se rapproche de la sortie, puis est publiée. Dans C # 8.0, un changement majeur se produira, C # supposera que tous les types sont considérés comme non nuls.

Selon Mads Torgersen:

Le problème est que les références nulles sont si utiles. En C #, ils sont la valeur par défaut de chaque type de référence. Quelle serait la valeur par défaut d'autre? Quelle autre valeur aurait une variable, jusqu'à ce que vous puissiez décider quoi d'autre lui attribuer? Avec quelle autre valeur pourrions-nous ouvrir un tableau de références fraîchement alloué, jusqu'à ce que vous arriviez à le remplir?

En outre, parfois nul est une valeur sensible en soi. Parfois, vous voulez représenter le fait que, par exemple, un champ n'a pas de valeur. Qu'il est correct de passer «rien» pour un paramètre. L'accent est mis sur parfois, cependant. Et c'est là une autre partie du problème: les langages comme C # ne vous permettent pas d'exprimer si un nul ici est une bonne idée ou non.

La résolution décrite par Mads est donc:

  1. Nous pensons qu'il est plus courant de vouloir qu'une référence ne soit pas nulle. Les types de référence nulles seraient les plus rares (bien que nous n'ayons pas de bonnes données pour nous dire de combien), ce sont donc eux qui devraient nécessiter une nouvelle annotation.

  2. Le langage a déjà une notion de - et une syntaxe pour - les types valeur Nullable. L'analogie entre les deux rendrait l'ajout de langue plus facile sur le plan conceptuel et plus simple sur le plan linguistique.

  3. Il semble juste que vous ne devriez pas vous charger ou charger votre consommateur de valeurs nulles encombrantes à moins que vous n'ayez activement décidé que vous les vouliez. Les valeurs nulles, et non leur absence, devraient être la chose à laquelle vous devez explicitement opter.

Un exemple de la fonctionnalité souhaitée:

public class Person
{
     public string Name { get; set; } // Not Null
     public string? Address { get; set; } // May be Null
}

L'aperçu est disponible pour Visual Studio 2017, version 15.5.4+.

Greg
la source
1
Est-ce que quelqu'un sait si cela a fini par faire partie de C # 8.0?
TroySteven
@TroySteven Oui, vous devez y adhérer via les paramètres de Visual Studio.
Greg
@TroySteven Voici la documentation à ce sujet. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Greg
Bien, il semble que vous deviez l'activer avant de commencer à fonctionner, c'est bien pour la compatibilité ascendante.
TroySteven
15

Je sais que c'est une TRÈS vieille question, mais celle-ci manquait ici:

Si vous utilisez ReSharper / Rider, vous pouvez utiliser le cadre annoté .

Edit : Je viens d'avoir un -1 aléatoire pour cette réponse. C'est très bien. Sachez simplement qu'elle est toujours valide, même si ce n'est plus l'approche recommandée pour les projets C # 8.0 + (pour comprendre pourquoi, voir la réponse de Greg ).

rsenna
la source
1
Fait intéressant, par défaut, votre application compilée n'aura pas de référence à JetBrains.Annotations.dll afin que vous n'ayez pas à le distribuer avec l'application: Comment utiliser les annotations JetBrains pour améliorer les inspections ReSharper
Lu55
9

Découvrez les validateurs dans la bibliothèque d'entreprise. Vous pouvez faire quelque chose comme:

private MyType _someVariable = TenantType.None;
[NotNullValidator(MessageTemplate = "Some Variable can not be empty")]
public MyType SomeVariable {
    get {
        return _someVariable;
    }
    set {
        _someVariable = value;
    }
}

Puis dans votre code lorsque vous souhaitez le valider:

Microsoft.Practices.EnterpriseLibrary.Validation.Validator myValidator = ValidationFactory.CreateValidator<MyClass>();

ValidationResults vrInfo = InternalValidator.Validate(myObject);
Pas moi
la source
0

pas le plus joli mais:

public static bool ContainsNullParameters(object[] methodParams)
{
     return (from o in methodParams where o == null).Count() > 0;
}

vous pouvez également faire preuve de plus de créativité dans la méthode ContainsNullParameters:

public static bool ContainsNullParameters(Dictionary<string, object> methodParams, out ArgumentNullException containsNullParameters)
       {
            var nullParams = from o in methodParams
                             where o.Value == null
                             select o;

            bool paramsNull = nullParams.Count() > 0;


            if (paramsNull)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var param in nullParams)
                    sb.Append(param.Key + " is null. ");

                containsNullParameters = new ArgumentNullException(sb.ToString());
            }
            else
                containsNullParameters = null;

            return paramsNull;
        }

bien sûr, vous pouvez utiliser un intercepteur ou une réflexion, mais ils sont faciles à suivre / à utiliser avec peu de frais généraux

Jeff Grizzle
la source
3
Très mauvaise pratique en utilisant de telles requêtes: return (from o in methodParams where o == null).Count() > 0; Utilisation: return methodParams.Any(o=>o==null);ce sera beaucoup plus rapide dans les grandes collections
Yavanosta
Pourquoi faire passer l'exception? Pourquoi ne pas simplement le lancer?
Martin Capodici
-4

Ok, cette réponse est un peu tardive, mais voici comment je résous le problème:

public static string Default(this string x)
{
    return x ?? "";
}

Utilisez cette méthode d'extension pour pouvoir traiter les chaînes nulles et vides comme la même chose.

Par exemple

if (model.Day.Default() == "")
{
    //.. Do something to handle no Day ..
}

Pas idéal je sais car vous devez vous rappeler d'appeler default partout mais c'est une solution.

Martin Capodici
la source
5
comment est-ce meilleur / plus facile que les vérifications (x == null)? ou la fonction String.IsNotNullOrEmpty.
Batavia
Pas beaucoup mieux que string.IsNotNullOrEmptyvraiment autre que le sucre d'avoir ce qui vous tient à cœur sur la gauche. Peut être alimenté dans d'autres fonctions (longueur, concaténation) etc. Marginally mieux.
Martin Capodici
@MartinCapodici En outre, cela crée l'illusion que vous pouvez appeler la méthode d'instance ( Default()) en toute sécurité sur un null ( model.Day). Je sais que les méthodes d'extension ne sont pas vérifiées contre null, mais mes yeux ne sont pas conscients et ils ont déjà imaginé le ?dans le code: model?.Day?.Default():)
Alb