Laden...

Hinweis: C# 8.0 nullable reference types -- Achtung vor falscher Sicherheit

Erstellt von gfoidl vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.796 Views
gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 4 Jahren
Hinweis: C# 8.0 nullable reference types -- Achtung vor falscher Sicherheit

Hallo zusammen,

wie vllt. bekannt ist wird mit C# 8.0 der Kampf dem Billion Dollar MistakeTony Hoare) angesagt. Siehe dazu Design with nullable reference types.

Allerdings darf man sich v.a. bei öffentlichen (public) APIs nicht in falscher Sicherheit wiegen.

Diese Gefahr / Tücke will ich mit nachfolgenden simplen Beispiel demonstrieren.


#nullable enable

    public class Person
    {
        public string Name { get; }

        public Person(string name) => this.Name = name;
    }
}

Für die öffentliche Klasse Person wurde das "nullable feature" explizit aktiviert*, somit könnte man meinen, dass name eben nicht null sein darf.
Das ist für diese Klasse auch korrekt**, andernfalls hätte der Parameter als string? name deklariert werden müssen um null zu erlauben.

Wenn nun ein Benutzer unserer Klasse Person Nullable nicht aktiviert hat, wie im folgenden Code


    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person(null);
            Console.WriteLine(person.Name.Length);
        }
    }

, so kompiliert das Programm ohne Fehler / Warnung, zur Laufzeit gibt es aber eine NullReferenceException, da nirgends validiert wurde ob das Konstruktor-Argument name ungleich Null ist.

Wäre für obigen Code Nullable auch aktiviert worden, so hätte der C#-Compiler eine Warnung / Fehler ausgegeben.
Beim Erstellen eines "public apis" können wir aber nicht davon ausgehen, dass jeder Benutzer Nullable aktiviert hat und somit können wir in die vorhin demonstrierte Falle tappen.

Somit will ich mit diesem Beitrag den Hinweis ausprechen:
Auch mit C# 8.0 Nullable müssen public api auf null validiert werden!.

D.h. eingangs erwähnte Person-Klasse ist auch in Zukunft korrekt zu schreiben:


#nullable enable

    public class Person
    {
        public string Name { get; }

        public Person(string? name) => this.Name = name ?? throw new ArgumentNullException(nameof(name));
    }

Hier wurde als Typ string? angegeben, damit verdeutlicht wird dass Verwender dieser Klasse auch null dürfen.

Wäre als Typ string angegeben und der Verwender dieser Klasse hat Nullable sowie TreatWarningsAsErrors aktiviert, so ließe sich der Code mit null gar nicht kompilieren. Dies kann u.U. ein Hindernis sein. Daher bevorzuge ich (momentan***) die Variante mit string?.

* es ist auch eine Aktivierung via Projekteinstellungen möglich, ich empfehle aber die Aktivierung per Compiler-Direktive vorzunehmen, da so beim Lesen vom Code sofort ersichtlich dass "opt-in" für Nullable durchgeführt wurde.

** korrekt im Rahmen der vom Compiler durchgeführten statischen Codeanalyse, die entweder als Warnung od. bei <TreatWarningsAsErrors>true</TreatWarningsAsErrors> (in der csproj) als Fehler ausgegeben wird

*** Nullable ist noch relativ neu, daher kann es sein dass sich meine Präferenz dafür ändert und string (also ohne das Zulassen von null) die bessere Wahl ist

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

656 Beiträge seit 2008
vor 4 Jahren

Interessant, ich hatte irgendwo mal gelesen dass der Plan wäre, in dem Fall die Checks/Exceptions/Contracts vom Compiler generiert werden sollen.

Kommt die NRE beim Zugriff auf person.Name.Length oder bereits beim Konstruktor-Aufruf new Person(null)?

Edit: Wies scheint hatte ich hier ein altes Proposal für den dammit Operator im Kopf, dass im Nullable Kontext folgende Syntax erlaubt sein soll(te):

#nullable enable
public static void MyFunc(string name!) { /* ... */ }

// becomes:
public static void MyFunc(string name)
{
    if (name is null) throw new ArgumentNullException(nameof(name));
    /* ... */
}
1.040 Beiträge seit 2007
vor 4 Jahren
  
#nullable enable  
  

Für die öffentliche Klasse Person wurde das "nullable feature" explizit aktiviert*, somit könnte man meinen, dass name eben nicht null sein darf.

Sowas verwirrt mich immer, das Feature heißt "nullable", wenn man es einschaltet aktiviert man aber eigtl. "non-nullable".
Wahrscheinlich verstehe ich wieder irgendwas nicht, aber für mich isses verdreht. 🤔

16.806 Beiträge seit 2008
vor 4 Jahren

Es aktiviert nullable reference types. Das Feature ist schon gut so genannt.

1.040 Beiträge seit 2007
vor 4 Jahren

Okay, dann verstehe ich es nicht.
Warum darf name dann eben nicht null sein?

16.806 Beiträge seit 2008
vor 4 Jahren

Mit C# 8 kommt die Möglichkeit, dass Referenztypen explizit nicht mehr null sein können.
Tutorial: Express your design intent more clearly with nullable and non-nullable reference types

Früher wusste man bei

public PersonEntity Person {get;set;}

nicht, ob Person nun null ist oder nicht - es musste immer alles geprüft werden.

Mit nullable reference types kann/muss man nun explizit angeben, wenn etwas null sein kann.
Das gilt auch für string.


#nullable enable
public PersonEntity Person {get;set;} // Kann nicht null sein

public PersonEntity? Person {get;set;} // Kann null sein

Das macht am Ende den Code weniger anfällig vor ReferenceExceptions.

1.040 Beiträge seit 2007
vor 4 Jahren

Ja, also doch richtig verstanden.
Dann wäre "non-nullable" irgendwie logischer, schließlich kann es ja nicht null sein.

16.806 Beiträge seit 2008
vor 4 Jahren

Nein, denn Du musst nun explizit angeben, dass es nullable ist.
Und man nennt es so, weil es opt-in ist.

non-nullable wäre nicht opt-in.

1.040 Beiträge seit 2007
vor 4 Jahren

Hä?

Du schreibst doch selbst:

  
#nullable enable  
public PersonEntity Person {get;set;} // Kann nicht null sein  
  

D.h. es ist nicht nullable.

EDIT: einfacher und verständlicher wäre wohl ein ? für string und Co. gewesen.

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 4 Jahren

Hallo p!lle,

konkret geht es um die Analogie zu Nullable types (wie z.B. int?), daher passt der Name mit Nullable Reference Types schon.

Mit #nullable enable werden die Nullable Reference Types als optionale Erweiterung der C#-Sprache aktiviert.

Optional ist das Ganze deshalb, damit für betehenden Code keine breaking-change passiert bzw. bestehender Code auch weiterhin kompiliert ohne Warnungen / Fehler zu generieren.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 4 Jahren

Hallo BhaaL,

Kommt die NRE beim Zugriff auf person.Name.Length oder bereits beim Konstruktor-Aufruf new Person(null)?

Das läuft ab wie bisher, also beim Zugriff auf Length, da Name null ist.

Wies scheint hatte ich hier ein altes Proposal ...

Das soll in Zukunft auch kommen. Allerdings weiß ich nicht wie "Zukunft" hier definiert ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
224 Beiträge seit 2009
vor 4 Jahren

Ich stimme P!lle bei.

#nullable liest sich für mich auf dem ersten Blick als #nullableValuesAreNowAllowed und nicht als
#nullableExplicit oder #nullableReferenceTypes.

Die Benennung #nullableExplicit oder #nullableReferenceTypes hätte ich wesentlich besser empfunden.