Hallo zusammen,
wie vllt. bekannt ist wird mit C# 8.0 der Kampf dem Billion Dollar Mistake (© Tony 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!"
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));
/* ... */
}
#nullable enable
Für die öffentliche Klasse
Person
wurde das "nullable feature" explizit aktiviert*, somit könnte man meinen, dassname
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. 🤔
Es aktiviert nullable reference types
. Das Feature ist schon gut so genannt.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Okay, dann verstehe ich es nicht.
Warum darf name
dann eben nicht null sein?
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ja, also doch richtig verstanden.
Dann wäre "non-nullable" irgendwie logischer, schließlich kann es ja nicht null sein.
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
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!"
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!"
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.