Contrassegnare i parametri come NON nullable in C # /. NET?


98

Esiste un semplice attributo o contratto dati che posso assegnare a un parametro di funzione che impedisce il nullpassaggio in C # /. NET? Idealmente, questo controllerebbe anche in fase di compilazione per assicurarsi che il letterale nullnon venga utilizzato da nessuna parte per esso e al momento dell'esecuzione ArgumentNullException.

Attualmente scrivo qualcosa come ...

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

... per ogni argomento che mi aspetto non sia null.

Sulla stessa nota, c'è un contrario a Nullable<>cui quanto segue fallirebbe:

NonNullable<string> s = null; // throw some kind of exception

7
Con le versioni recenti di C #, l'utilizzo di "nameof ()" funziona meglio: throw new ArgumentNullException (nameof (arg)); In questo modo, se esegui il refactoring del nome, se si increspa nella dichiarazione di lancio
Flydog57

C # 8 ora supporta i tipi di riferimento nullable che è un'opzione del compilatore che puoi attivare nel tuo progetto. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Paul Stegler

Risposte:


69

Non c'è niente di disponibile in fase di compilazione, sfortunatamente.

Ho una soluzione un po ' hacker che ho pubblicato di recente sul mio blog, che utilizza una nuova struttura e conversioni.

In .NET 4.0 con la roba di Code Contracts , la vita sarà molto più bella. Sarebbe comunque abbastanza bello avere la sintassi del linguaggio e il supporto per la non annullabilità, ma i contratti di codice aiuteranno molto.

Ho anche un metodo di estensione in MiscUtil chiamato ThrowIfNull che lo rende un po 'più semplice.

Un ultimo punto: c'è qualche motivo per usare " if (null == arg)" invece di " if (arg == null)"? Trovo quest'ultimo più facile da leggere e il problema che il primo risolve in C non si applica a C #.


2
> Non c'è niente di disponibile in fase di compilazione, sfortunatamente. > ...> Sarebbe comunque abbastanza carino avere la sintassi del linguaggio e il supporto per la non annullabilità sono d'accordo. Vorrei anche che venissero segnalati errori in fase di compilazione.
AndrewJacksonZA

2
@ Jon, "if (arg = null)" non funziona se è presente un cast implicito a bool definito? Ammetto che possa sembrare perverso, ma compila ...
Thomas S. Trias,

11
@ ThomasS.Trias: Sì, in quel caso limite incredibilmente oscuro, combinato con un errore di battitura, combinato con una mancanza di test su quel codice, finiresti con un problema. A quel punto penso che tu abbia problemi più grandi però :)
Jon Skeet

4
@ Jon: concesso. Immagino che rinuncerò alle mie amate condizioni Yoda. :-)
Thomas S. Trias

1
@SebastianMach: Non credo che il motivo if (null == x)sia dovuto a differenze di linguaggio naturale. Credo che si tratti di uno stile obsoleto che si propaga solo attraverso esempi, ecc.
Jon Skeet,

21

So di essere incredibilmente in ritardo a questa domanda, ma sento che la risposta diventerà rilevante man mano che l'ultima grande iterazione di C # si avvicina al rilascio, quindi rilasciato. In C # 8.0 si verificherà una modifica importante, C # presupporrà che tutti i tipi non siano considerati null.

Secondo Mads Torgersen:

Il problema è che i riferimenti nulli sono così utili. In C #, sono il valore predefinito di ogni tipo di riferimento. Cos'altro sarebbe il valore predefinito? Quale altro valore avrebbe una variabile, finché non si deciderà cos'altro assegnarle? Con quale altro valore potremmo pavimentare una serie di riferimenti appena assegnati, fino a quando non arriverai a riempirla?

Inoltre, a volte null è un valore sensato in sé e per sé. A volte vuoi rappresentare il fatto che, diciamo, un campo non ha un valore. Che va bene passare "niente" per un parametro. L'enfasi è posta a volte, però. E qui sta un'altra parte del problema: linguaggi come C # non ti permettono di esprimere se un null qui è una buona idea o no.

Quindi la risoluzione delineata da Mads è:

  1. Riteniamo che sia più comune desiderare che un riferimento non sia nullo. I tipi di riferimento nullable sarebbero i più rari (anche se non abbiamo dati validi per dirci di quanto), quindi sono quelli che dovrebbero richiedere una nuova annotazione.

  2. Il linguaggio ha già una nozione di - e una sintassi per - tipi di valore nullable. L'analogia tra i due renderebbe l'aggiunta linguistica concettualmente più facile e linguisticamente più semplice.

  3. Sembra giusto che tu non debba gravare su te stesso o sul tuo consumatore con ingombranti valori nulli a meno che tu non abbia deciso attivamente che li desideri. I valori nulli, non l'assenza di essi, dovrebbero essere ciò che devi esplicitamente accettare.

Un esempio della funzionalità desiderata:

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

L'anteprima è disponibile per Visual Studio 2017, 15.5.4+ anteprima.


Qualcuno sa se questo è mai finito per far parte di C # 8.0?
TroySteven

@ TroySteven Sì, è necessario attivarlo tramite le impostazioni in Visual Studio.
Greg

@TroySteven Ecco la documentazione su di esso. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Greg

Bello, quindi sembra che devi abilitarlo prima che inizi a funzionare, è bello per la retrocompatibilità.
TroySteven

15

So che questa è una domanda MOLTO vecchia, ma qui mancava questa:

Se usi ReSharper / Rider puoi usare Annotated Framework .

Modifica : ho appena ricevuto un -1 casuale per questa risposta. Va bene. Basta essere consapevoli che è ancora valido, anche se non è più l'approccio consigliato per i progetti C # 8.0 + (per capire perché, vedere la risposta di Greg ).


1
È interessante notare che per impostazione predefinita la tua applicazione compilata non farà riferimento a JetBrains.Annotations.dll, quindi non devi distribuirlo con l'applicazione: Come utilizzare JetBrains Annotations per migliorare le ispezioni
ReSharper

9

Controlla i validatori nella libreria aziendale. Puoi fare qualcosa come:

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

Quindi nel tuo codice quando vuoi convalidarlo:

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

ValidationResults vrInfo = InternalValidator.Validate(myObject);

0

non il più carino ma:

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

potresti diventare più creativo anche con il metodo 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;
        }

ovviamente potresti usare un intercettore o un riflesso ma questi sono facili da seguire / usare con poco overhead


3
Pessima pratica nell'usare tali query: return (from o in methodParams where o == null).Count() > 0; Usa: return methodParams.Any(o=>o==null);sarà molto più veloce nelle grandi raccolte
Yavanosta

Perché svenire l'eccezione? Perché non lanciarlo?
Martin Capodici

-4

Ok questa risposta è un po 'in ritardo, ma ecco come la risolvo:

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

Usa questo metodo di estensione, quindi puoi trattare la stringa nulla e vuota come la stessa cosa.

Per esempio

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

Non è l'ideale, lo so perché devi ricordarti di chiamare default ovunque, ma è una soluzione.


5
come è migliore / più facile dei controlli (x == null)? o la funzione String.IsNotNullOrEmpty.
Batavia

Non molto meglio string.IsNotNullOrEmptydell'altro che lo zucchero di avere la cosa a cui tieni a sinistra. Può essere inserito in altre funzioni (lunghezza, concatenazione) ecc. Marginalmente migliore.
Martin Capodici

@MartinCapodici Inoltre, questo crea l'illusione di poter chiamare in sicurezza il metodo dell'istanza ( Default()) su un null ( model.Day). So che i metodi di estensione non sono verificati rispetto a null, ma i miei occhi non sono consapevoli e l'hanno già immaginato ?nel codice: model?.Day?.Default():)
Alb
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.