Oznacz parametry jako NIE dopuszczające wartości null w C # / .NET?

99

Czy istnieje prosty atrybut lub kontrakt danych, który mogę przypisać do parametru funkcji, który zapobiega nullprzekazywaniu w C # / .NET? Idealnie byłoby również sprawdzić w czasie kompilacji, aby upewnić się, że literał nullnie jest nigdzie używany do tego celu oraz w rzutach w czasie wykonywania ArgumentNullException.

Obecnie piszę coś takiego ...

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

... za każdy argument, którego nie spodziewam się null.

Z tej samej uwagi, czy istnieje odwrotność do sytuacji, w Nullable<>której poniższe zawiodłyby:

NonNullable<string> s = null; // throw some kind of exception
Neil C. Obremski
źródło
7
W nowszych wersjach C # użycie „nameof ()” działa lepiej: wyrzuć nowy ArgumentNullException (nameof (arg)); W ten sposób, jeśli zmienisz nazwę, jeśli
pojawi się
C # 8 obsługuje teraz typy odwołań dopuszczające wartość null, która jest opcją kompilatora, którą można włączyć w projekcie. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Paul Stegler,

Odpowiedzi:

69

Niestety w czasie kompilacji nic nie jest dostępne.

Mam trochę hakerskie rozwiązanie, które niedawno opublikowałem na swoim blogu, które wykorzystuje nową strukturę i konwersje.

W .NET 4.0 z zawartością Code Contracts życie będzie o wiele przyjemniejsze. Byłoby nadal całkiem fajnie mieć rzeczywistą składnię języka i obsługę braku wartości null, ale kontrakty kodu bardzo pomogą.

Mam również metodę rozszerzenia w MiscUtil o nazwie ThrowIfNull, która sprawia, że ​​jest to nieco prostsze.

Ostatnia kwestia - jakikolwiek powód, dla którego warto użyćif (null == arg) ” zamiast „ if (arg == null)”? Uważam, że ten drugi jest łatwiejszy do odczytania, a problem, który rozwiązuje ten pierwszy w C, nie dotyczy C #.

Jon Skeet
źródło
2
> Niestety, w czasie kompilacji nic nie jest dostępne. > ...> Byłoby całkiem fajnie mieć rzeczywistą składnię języka i wsparcie w zakresie braku wartości null Zgadzam się. Chciałbym również zobaczyć zgłaszane błędy w czasie kompilacji.
AndrewJacksonZA
2
@Jon, czy "if (arg = null)" nie działa, jeśli zdarzy się, że istnieje niejawne rzutowanie na zdefiniowaną wartość bool? Przyznaję, że może się to wydawać perwersyjne, ale kompiluje ...
Thomas S. Trias
11
@ ThomasS.Trias: Tak, w tym niesamowicie niejasnym przypadku krawędzi, w połączeniu z literówką, w połączeniu z brakiem testów dotyczących tego kodu, skończyłbyś z problemem. W tym momencie myślę, że masz już większe problemy :)
Jon Skeet
4
@Jon: Zgoda. Chyba zrezygnuję z warunków mojego ukochanego Yody. :-)
Thomas S. Trias
1
@SebastianMach: Nie wierzę jednak, że powodem if (null == x)są naturalne różnice językowe. Uważam, że tak naprawdę chodzi o przestarzały styl, który propaguje się tylko poprzez przykłady itp.
Jon Skeet,
22

Wiem, że jestem niesamowicie spóźniony z tym pytaniem, ale czuję, że odpowiedź nabierze znaczenia, gdy ostatnia główna iteracja języka C # będzie bliżej wydania, a następnie wydania. W C # 8.0 nastąpi poważna zmiana, C # założy, że wszystkie typy nie są null.

Według Madsa Torgersena:

Problem polega na tym, że zerowe odwołania są tak przydatne. W C # są one wartością domyślną każdego typu referencyjnego. Jaka byłaby inna wartość domyślna? Jaką inną wartość miałaby zmienna, dopóki nie zdecydujesz, co jeszcze jej przypisać? Jaką inną wartością moglibyśmy utorować świeżo przydzieloną tablicę odniesień, dopóki nie zajmiesz się jej wypełnieniem?

Czasami wartość zerowa jest sensowną wartością samą w sobie. Czasami chcesz przedstawić fakt, że, powiedzmy, pole nie ma wartości. Że można przekazać „nic” dla parametru. Czasami jednak nacisk kładzie się na. I tu pojawia się kolejna część problemu: języki takie jak C # nie pozwalają na wyrażenie, czy null tutaj jest dobrym pomysłem, czy nie.

Tak więc rezolucja nakreślona przez Madsa to:

  1. Uważamy, że częściej chce się, aby odniesienie nie było zerowe. Typy odwołań dopuszczające wartość zerową byłyby rzadszym rodzajem (chociaż nie mamy dobrych danych, aby powiedzieć nam, o ile), więc to one powinny wymagać nowej adnotacji.

  2. Język ma już pojęcie - i składnię - typów wartości dopuszczających wartość null. Analogia między nimi sprawiłaby, że dodawanie języka byłoby koncepcyjnie łatwiejsze i językowo prostsze.

  3. Wydaje się słuszne, że nie powinieneś obciążać siebie ani swojego konsumenta uciążliwymi wartościami zerowymi, chyba że aktywnie zdecydujesz, że ich chcesz. Null, a nie ich brak, powinny być tym, na co musisz wyraźnie wyrazić zgodę.

Przykład pożądanej funkcji:

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

Wersja zapoznawcza jest dostępna dla programu Visual Studio 2017, 15.5.4+ w wersji zapoznawczej.

Greg
źródło
1
Czy ktoś wie, czy to kiedykolwiek skończyło się jako część C # 8.0?
TroySteven
@TroySteven Tak, musisz się na to zdecydować poprzez ustawienia w programie Visual Studio.
Greg
@TroySteven Oto dokumentacja na ten temat. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Greg
Fajnie, więc wygląda na to, że musisz go włączyć, zanim zacznie działać, to jest dobre dla kompatybilności wstecznej.
TroySteven
15

Wiem, że to BARDZO stare pytanie, ale tego tutaj brakowało:

Jeśli używasz ReSharper / Rider, możesz użyć Annotated Framework .

Edycja : właśnie otrzymałem losową -1 dla tej odpowiedzi. W porządku. Po prostu pamiętaj, że jest on nadal aktualny, mimo że nie jest już zalecanym podejściem do projektów C # 8.0 + (aby zrozumieć, dlaczego, zobacz odpowiedź Grega ).

rsenna
źródło
1
Co ciekawe, domyślnie Twoja skompilowana aplikacja nie będzie miała odniesienia do pliku JetBrains.Annotations.dll, więc nie musisz jej dystrybuować razem z aplikacją: Jak używać adnotacji JetBrains, aby usprawnić inspekcje
ReSharper
9

Sprawdź walidatory w bibliotece korporacyjnej. Możesz zrobić coś takiego:

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

Następnie w swoim kodzie, jeśli chcesz go zweryfikować:

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

ValidationResults vrInfo = InternalValidator.Validate(myObject);
Nie ja
źródło
0

nie najładniejsze ale:

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

możesz być bardziej kreatywny w metodzie 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;
        }

oczywiście możesz użyć przechwytywacza lub odbicia, ale są one łatwe do śledzenia / używania z niewielkim narzutem

Jeff Grizzle
źródło
3
Bardzo zła praktyka z użyciem takich zapytań: return (from o in methodParams where o == null).Count() > 0; Użyj: return methodParams.Any(o=>o==null);będzie znacznie szybciej przy dużych kolekcjach
Yavanosta
Po co omijać wyjątek? Dlaczego go po prostu nie wyrzucić?
Martin Capodici
-4

Ok, ta odpowiedź jest trochę spóźniona, ale oto jak ją rozwiązuję:

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

Użyj tej metody rozszerzenia, a następnie możesz traktować pusty i pusty ciąg jako to samo.

Na przykład

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

Wiem, że nie jest to idealne rozwiązanie, ponieważ musisz pamiętać, aby wywoływać domyślne wszędzie, ale jest to jedno rozwiązanie.

Martin Capodici
źródło
5
w jaki sposób jest to lepsze / łatwiejsze niż sprawdzanie (x == null)? lub funkcję String.IsNotNullOrEmpty.
Batavia,
Niewiele lepsze niż string.IsNotNullOrEmptynaprawdę inne niż cukier wynikający z posiadania tego, na czym ci zależy, po lewej stronie. Może być wprowadzany do innych funkcji (długość, konkatenacja) itp. Nieznacznie lepiej.
Martin Capodici
@MartinCapodici Ponadto stwarza to złudzenie, że można bezpiecznie wywołać metodę instancji method ( Default()) na null ( model.Day). Wiem, że metody rozszerzeń nie są sprawdzane pod kątem wartości null, ale moje oczy nie są tego świadome i już sobie wyobrazili ?w kodzie: model?.Day?.Default():)
Alb