I've recently seen the following code:
public class Person
{
//line 1
public string FirstName { get; }
//line 2
public string LastName { get; } = null!;
//assign null is possible
public string? MiddleName { get; } = null;
public Person(string firstName, string lastName, string middleName)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName;
}
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
MiddleName = null;
}
}
Basically, I try to dig into new c# 8 features. One of them is NullableReferenceTypes.
Actually, there're a lot of articles and information about it already. E.g. this article is quite good.
But I didn't find any information about this new statement null!
Can someone provide me an explanation for it?
Why do I need to use this?
And what is the difference between line1 and line2?
TL;DR
The key to understanding what null! means is understanding the ! operator. You may have used it before as the "not" operator. However, since C# 8.0 and its new "nullable-reference-types" feature, the operator got a second meaning. It can be used on a type to control Nullability, it is then called the "Null Forgiving Operator".
Basically, null! applies the ! operator to the value null. This overrides the nullability of the value null to non-nullable, telling the compiler that null is a "non-null" type.
Typical usage
Assuming this definition:
class Person
{
// Not every person has a middle name. We express "no middle name" as "null"
public string? MiddleName;
}
The usage would be:
void LogPerson(Person person)
{
Console.WriteLine(person.MiddleName.Length); // WARNING: may be null
Console.WriteLine(person.MiddleName!.Length); // No warning
}
This operator basically turns off the compiler null checks for this usage.
Technical Explanation
The groundwork that you will need to understand what null! means.
Null Safety
C# 8.0 tries to help you manage your null-values. Instead of allowing you to assign null to everything by default, they have flipped things around and now require you to explicitly mark everything you want to be able to hold a null value.
This is a super useful feature, it allows you to avoid NullReferenceExceptions by forcing you to make a decision and enforcing it.
How it works
There are 2 states a variable can be in - when talking about null-safety.
Nullable - Can be null.
Non-Nullable - Can not be null.
Since C# 8.0 all reference types are non-nullable by default.
Value types have been non-nullable since C# 2.0!
The "nullability" can be modified by 2 new (type-level) operators:
! = from Nullable to Non-Nullable
? = from Non-Nullable to Nullable
These operators are counterparts to one another.
The Compiler uses the information, you define with those operators, to ensure null-safety.
Examples
? Operator usage.
This operator tells the compiler that a variable can hold a null value. It is used when defining variables.
Nullable string? x;
x is a reference type - So by default non-nullable.
We apply the ? operator - which makes it nullable.
x = null Works fine.
Non-Nullable string y;
y is a reference type - So by default non-nullable.
y = null Generates a warning since you assign a null value to something that is not supposed to be null.
Nice to know: Using object? is basically just syntactic sugar for System.Nullable<object>
! Operator usage.
This operator tells the compiler that something that could be null, is safe to be accessed. You express the intent to "not care" about null safety in this instance. It is used when accessing variables.
string x;
string? y;
x = y
Illegal! Warning: "y" may be null
The left side of the assignment is non-nullable but the right side is nullable.
So it does not work, since it is semantically incorrect
x = y!
Legal!
y is a reference type with the ? type modifier applied so it is nullable if not proven otherwise.
We apply ! to y which overrides its nullability settings to make it non-nullable
The right and left side of the assignment are non-nullable. Which is semantically correct.
WARNING The ! operator only turns off the compiler-checks at a type-system level - At runtime, the value may still be null.
Use carefully!
You should try to avoid using the Null-Forgiving-Operator, usage may be the symptom of a design flaw in your system since it negates the effects of null-safety you get guaranteed by the compiler.
Reasoning
Using the ! operator will create very hard to find bugs. If you have a property that is marked non-nullable, you will assume you can use it safely. But at runtime, you suddenly run into a NullReferenceException and scratch your head. Since a value actually became null after bypassing the compiler-checks with !.
Why does this operator exist then?
There are valid use-cases (outlined in detail below) where usage is appropriate. However, in 99% of the cases, you are better off with an alternative solution. Please do not slap dozens of !'s in your code, just to silence the warnings.
In some (edge) cases, the compiler is not able to detect that a nullable value is actually non-nullable.
Easier legacy code-base migration.
In some cases, you just don't care if something becomes null.
When working with Unit-tests you may want to check the behavior of code when a null comes through.
Ok!? But what does null! mean?
It tells the compiler that null is not a nullable value. Sounds weird, doesn't it?
It is the same as y! from the example above. It only looks weird since you apply the operator to the null literal. But the concept is the same. In this case, the null literal is the same as any other expression/type/value/variable.
The null literal type is the only type that is nullable by default! But as we learned, the nullability of any type can be overridden with ! to non-nullable.
The type system does not care about the actual/runtime value of a variable. Only its compile-time type and in your example the variable you want to assign to LastName (null!) is non-nullable, which is valid as far as the type-system is concerned.
Consider this (invalid) piece of code.
object? null;
LastName = null!;
When the "nullable reference types" feature is turned on, the compiler tracks which values in your code it thinks may be null or not. There are times where the compiler could have insufficient knowledge.
For example, you may be using a delayed initialization pattern, where the constructor doesn't initialize all the fields with actual (non-null) values, but you always call an initialization method which guarantees the fields are non-null. In such case, you face a trade-off:
if you mark the field as nullable, the compiler is happy, but you have to un-necessarily check for null when you use the field,
if you leave the field as non-nullable, the compiler will complain that it is not initialized by the constructors (you can suppress that with null!), then the field can be used without null check.
Note that by using the ! suppression operator, you are taking on some risk. Imagine that you are not actually initializing all the fields as consistently as you thought. Then the use of null! to initialize a field covers up the fact that a null is slipping in. Some unsuspecting code can receive a null and therefore fail.
More generally, you may have some domain knowledge: "if I checked a certain method, then I know that some value isn't null":
if (CheckEverythingIsReady())
{
// you know that `field` is non-null, but the compiler doesn't. The suppression can help
UseNonNullValueFromField(this.field!);
}
Again, you must be confident of your code's invariant to do this ("I know better").
null! is used to assign null to non-nullable variables, which is a way of promising that the variable won't be null when it is actually used.
I'd use null! in a Visual Studio extension, where properties are initialized by MEF via reflection:
[Import] // Set by MEF
VSImports vs = null!;
[Import] // Set by MEF
IClassificationTypeRegistryService classificationRegistry = null!;
(I hate how variables magically get values in this system, but it is what it is.)
I also use it in unit tests to mark variables initialized by a setup method:
public class MyUnitTests
{
IDatabaseRepository _repo = null!;
[OneTimeSetUp]
public void PrepareTestDatabase()
{
...
_repo = ...
...
}
}
If you don't use null! in such cases, you'll have to use an exclamation mark every single time you read the variable, which would be a hassle without benefit.
Note: cases where null! is a good idea are fairly rare. I treat it as somewhat of a last resort.
I don't believe this question can be discussed without specific C# version being used.
public string RpyDescription { get; set; } = null!;
This is common in .NET 6 Core ... in fact it's required if you want to describe a string as not being nullable. One reason this exists is when one is working with SQL databases where a field can be set to "Allow Null". Even more so when working with JSON structures that accept null. EF needs to know.
Reference Type (Heap - pointer to memory location where the data is stored) of Value Type (Stack - memory location where data is stored).
.NET 6 (C#10) enables nullable context for the project templates by default (prior to this nullable context is disabled by default).
In EF/Core, it's very important to understand relationship between database null and model/entities null.
This question needs to be updated, in C# 10 in object relational mapping, this operator, combined with the ? operator is critical, this is a way to tell other coders on the project, future coders, and remind yourself how the data is going to end up being consumed and the null rules regarding that data.
public string Data { get; set; } = null!;
public string? Nullable { get; set; }
The beauty of this is that you don't have to go and look at the API docs(which you might not have), or go look through your database (which you might not even have access to). You already know by glancing at the class what the rules regarding null values are.
The downside is that if you instantiate the class, and don't have the information you need to instantiate the NOT NULL values, you will get strange default values that don't always make sense. First of all this doesn't happen nearly as commonly as people think and often comes from lazy programming. When you instantiate an instance of a class. You should calculating or assigning those NOT NULL properties. Right away. If you declare a car, and in your database or API cars have wheels, if you declare a car without assigning property values to the wheel.
Then did you truly instantiate the car in a meaningful way? You certainly didn't define the car the way the Database understands a car, it's a car with no wheels and it shouldn't be, doing this might be convenient but it goes against basic principles of object oriented programming.
are there exceptions for example perhaps the value can't be known at that point in time or until other events have transpired in these edge cases. Create a meaningful default value manually, if your default value hits the database you will know EXACTLY what's wrong
for people discussing unit tests why would you test the null case when the null case is impossible in the context of the object there is no need to have an understanding of how a car without wheels would behave for example. This is a silly, badly designed test.
I would go so far as to say that when it comes to strings this operator should be used the overwhelming majority of the time. When we declare int, or bool, or float, we have the understanding that these cannot be null unless we say so explicitly. Why in the world would you advocate for a different rule for strings!? the fact that we couldn't do this with strings previously was a design flaw.
Related
Since C# 10, Nullable will disabled by default.
I already saw so much article and video about Nullable, They just saying we won't worry Null reference exception any more.
Also they keep saing there is so much way to use it by: Disable, Enable, Warning, Annotations.....bla bla bla.
And They Introduce us a lot of ways to use it with : ?., ??, ??=, NotNullWhenTrue, NotNullWhenFalse...etc
But I didn't saw anyone tell us: How to use it when it disabled.
We have a lot of scenario to use null before.
1. Property:
// What is the default value when nullable disabled , and how way we should use it?
Public string Name { get; set; }
2. Linq:
Person model = PersenList.Where(x => x.id == id).FirstOrDefault();
if (null != model)
{
// Do something
}
// How should we do when nullable diabled, what is the default value now, and how way we could check it a default value or not?
3.Temporary variable:
string ageDescription = null;
if (student.Age > 13)
{
ageDescription = "X";
}
if (student.Age > 15)
{
ageDescription = "XL";
}
if (student.Age > 18)
{
ageDescription = "XXL";
}
System.Diagnostics.Debug.WriteLine($"The Student size: {(ageDescription ?? "Not found")}");
// What should we do in this case, bring "Not found" at the began always?
Or
string description = null;
if (student.Score < 70)
{
description = "C";
}
if (student.Score > 70)
{
description = "B";
}
if (student.Score > 80)
{
description = "A";
}
if (student.Score > 90)
{
description = "AA";
}
student.description = description;
JsonConvert.Serialize(student, {with Ignore Null Option for save more space});
// How do we save the json size and space, if we disable the nullable?
Or
string value = null;
try {
value = DoSomething();
if (value == "Something1")
{
Go1();
}
if (value == "Something2")
{
Go2();
}
if (value == "Something3")
{
Go3();
}
} catch (Exception ex)
{
if (null == value)
{
GoNull();
}
else
{
GoOtherButException(ex)
}
}
// How should we handle this kind of problem?
4.Entity Framework
//The tables always has null field and how we deal with it when nullable disabled?
I know there are much more scenario we might handle. I feel like they just bluffing there are so many Nullable feature are Awesome, but not give us any direction or good way to point out.
I hope someone already use C#10, pointed us how to change our old fashioned code style after disabled Nullable, and give us some example to show us how we should do in the future. Thanks
--------Update1--------
I add some more variable examples.
--------Update2--------
Some foks just said that we could use whatevery way we want. that's based on you requirement. If you want to use it, just simply add ? like:
string? name = null
But I more hope they could just tell me: use String.Empty replace the null in every place. Ha ha....
But In that case every place I still need to check if ( variable != String.Empty), but we could avoid null reference exception, also I'm not sure String.Empty will take how much spaces in memory.
So why don't anyone tell us to do that: when they told us disable the nullable, we need to changing our code style with how way?
Another thing that is I really can't get that How do we check the default value of Linq when use FirstOrDefault(), before we always use if (null != model).
Maybe I really want to know: How is the world like in the future if we all disable the nullable.
First thing that I feel like needs clearing up is that nullable reference types being enabled will not impact if your code can build or not. It has no impact on default values.
Nullable reference types are meant to enable you to Express your design intent more clearly with nullable and non-nullable reference types. As is the title of the awesome tutorials: nullable reference types and I highly suggest anyone trying to dive into this feature to read it.
The nullable reference types feature allows you to explicitly state: I never expect this (nullable type) property to be unknown. Thus it should always have a value.
To state that you expect a property to always have a value you define it as usual
public string Text { get; set; }.
To explicitly state that a property is not certain to have a value it would be defined with a '?' after the type
public string? Text { get; set; }
Hopefully the intent of this feature is now, more or less, clear. Let's dig into your concrete questions.
1. Property:
your question:
// What is the default value when nullable disabled , and how way we should use it?
public string Name { get; set; }
A string or class property without initialization will still be null. An int without explicit initialization will still be 0 and a boolean without explicit initialization will still default to false. As not to repeat the same text on how to use it I will only repeat this. You should use it to Express your design intent more clearly with nullable and non-nullable reference types. It is up to you to mark all classes in the solution with whether or not a property has this characteristic. No this is not fun to do. But you also don't need to do it in 1 go. You can easily just enable the feature (have a ton of warnings) and steadily progress towards a solution that has more or less all classes covered on this.
2. Linq:
Person model = PersonList.Where(x => x.id == id).FirstOrDefault();
// How should we do when nullable diabled, what is the default value now, and how way we could check it a default value or not?
If you have a Linq query where you are not certain you can actually find what you are looking for (as is the reality for a lot of cases) .FirstOrDefault() can still be used. But Person model should express that it might potentially be null by changing it to Person? model.
3.Temporary variable:
You want to have a temp variable that is null at first and assign it conditionally? No problem, just add that magic '?' after it.
OR! If you definitely know you are going to give it a value why not assign an empty value to it? For example:
string description = string.Empty;
if (student.Score < 70)
description = "C";
// ... some more conditions ...
student.description = description;
4. Entity Framework
I reckon that in the data access layer it will become super clear which properties to mark as will never be null and the ones that might. Just take a look at the database model and queries / stored procedures and the null checks you do before writing to the database. Everything that is unable to be null in your database will be in your data model class and everything that might contain will be marked with the '?' after the type in the data model class.
What is the world like if Nullable Reference Types are enabled?
Your IDE will show you warnings of potential unexpected null. This will, if the team works to reduce warnings, result in properties that explicitly state if they could be null, or not. This is done by adding a "?" after the property type. If you are certain that something is not null you can add a "!" to suppress the warning.
For DTO classes the "!" is something that is used often. This is due to the necessity for the DTO class to have an empty constructor and its properties to be settable using set; or init; properties. A warning will be shown. Here either the warning can be disabled for the file or the properties can be initialized with a default value (which can be null) with the warning suppressed using the "!". For both ways the properties should be null guarded prior to using them.
Suppress the warning by setting a default value using "!":
public List<string> Comments { get; set; } = null!;
A usual occurring phenomenon of this is that you will most likely see null guard clauses decreasing. This can be done if you are certain that the code being called is guaranteed to comply with the nullable references types feature. It is worth noting that these properties might still be null and thus you open yourself up for null reference exceptions instead of argument null exceptions. Nonetheless in my opinion this is worth the clarity and reduced size. But to each their own on how they see this. Stackoverflow question: When to null-check arguments with nullable reference types enabled
The end result is that your code will clearly express which variables are expected, and thus should be worked with in a nullable manner, to be non existing sometimes. This might result in fewer guard clauses with the benefits of less (boilerplate) code if possible
References
I really do hope that I managed to help you out a bit. If you have any more thirst from knowledge beside the tutorial here are some more really good docs regarding nullable reference types:
tutorials: nullable reference types
Working with Nullable Reference Types
Nullable reference types
I want to use non-nullable reference type in my dto:s/api objects, without getting warnings and without defeating the entire purpose by using them by using the bang operator.
Lets say I consume from an GraphQL-endpoints that returns a hierarchy of complex objects. So normally I just deserialize the response with a type parameter...something like this
var customerInfo = mySerializer.Deserialize<CustomerInfo>(jsonResponseFromServer)
From the schema-file I have a guarantee that some of these objects and strings (inside CustomerInfo) are not null.
so I want to use non-nullable reference types in my DTOs. But now I get complaints about all these non-nullable properties, because the compiler has no clue about the GraphQL-scheme.
I'd really want to avoid writing humongous constructor for all these objects and If I did I wouldn't know how to get my serializer to use those anyway...
This must be a quite common problem, but I haven't found any recommended solution
Edit, from a comment: about the structure of my class, lets keep it simple, because the exact structure is not relevant
public class CustomerInfo
{
public string Name {get;set;}
public Address Address {get;set;}
//...
}
So...nothing special.
Non-nullable reference type is a syntactic addition which is only used in static code analysis, where compiler tries to prove that a null assignment is not attempted on a reference.
As mentioned in the comment, this has nothing to do with the GraphQL, nor with deserialization. It is entirely a C# question.
Another related issue is that there is no runtime check whether a null reference is assigned or not. Therefore, my first concern is in using non-nullable reference types in a DTO in the first place - what is the purpose of that if, at runtime, that reference can be set to null none the less, probably causing a NullReferenceException downstream that was not supposed to happen.
And, finally, the direct answer to the question. If a type declares fields or properties of non-nullable types, then for each of those, one of these must be present in code to avoid warnings:
Set to a non-null value in the constructor,
Initialized to a non-null value using the initializer syntax, or
Initialized to a null! to mark the intention to set to a non-null value later.
Regarding your particular design with DTOs, be warned that any non-nullable reference type could effectively be set to null later, should the data you are deserializing change in the future. You should still provide a constructor which guards against null inputs.
This isn't my first question about nullable reference types as it's been few months I'm experiencing with it. But the more I'm experiencing it, the more I'm confused and the less I see the value added by that feature.
Take this code for example
string? nullableString = default(string?);
string nonNullableString = default(string);
int? nullableInt = default(int?);
int nonNullableInt = default(int);
Executing that gives:
nullableString => null
nonNullableString => null
nullableInt => null
nonNullableInt => 0
The default value of an (non-nullable) integer has always been 0 but
to me it doesn't make sense a non-nullable string's default value is null.
Why this choice? This is opposed to the non-nullable principles we've always been used to.
I think the default non-nullable string's default value should have been String.Empty.
I mean somewhere deep down in the implementation of C# it must be specified that 0 is the default value of an int. We also could have chosen 1 or 2 but no, the consensus is 0. So can't we just specify the default value of a string is String.Empty when the Nullable reference type feature is activated? Moreover it seems Microsoft would like to activate it by default with .NET 5 projects in a near future so this feature would become the normal behavior.
Now same example with an object:
Foo? nullableFoo = default(Foo?);
Foo nonNullableFoo = default(Foo);
This gives:
nullableFoo => null
nonNullableFoo => null
Again this doesn't make sense to me, in my opinion the default value of a Foo should be new Foo() (or gives a compile error if no parameterless constructor is available).
Why by default setting to null an object that isn't supposed to be null?
Now extending this question even more
string nonNullableString = null;
int nonNullableInt = null;
The compiler gives a warning for the 1st line which could be transformed into an error with a simple configuration in our .csproj file: <WarningsAsErrors>CS8600</WarningsAsErrors>.
And it gives a compilation error for the 2nd line as expected.
So the behavior between non-nullable value types and non-nullable reference types isn't the same but this is acceptable since I can override it.
However when doing that:
string nonNullableString = null!;
int nonNullableInt = null!;
The compiler is completely fine with the 1st line, no warning at all.
I discovered null! recently when experiencing with the nullable reference type feature and I was expecting the compiler to be fine for the 2nd line too but this isn't the case. Now I'm just really confused as for why Microsoft decided to implement different behaviors.
Considering it doesn't protect at all against having null in a non-nullable reference type variable, it seems this new feature doesn't change anything and doesn't improve developers' lives at all (as opposed to non-nullable value types which could NOT be null and therefore don't need to be null-checked)
So at the end it seems the only value added is just in terms of signatures. Developers can now be explicit whether or not a method's return value could be null or not or if a property could be null or not (for example in a C# representation of a database table where NULL is an allowed value in a column).
Beside that I don't see how can I efficiently use this new feature, could you please give me other useful examples on how you use nullable reference types?
I really would like to make good use of this feature to improve my developer's life but I really don't see how...
Thank you
You are very confused by how programming language design works.
Default values
The default value of an (non-nullable) integer has always been 0 but to me it doesn't make sense a non-nullable string's default value is null. Why this choice? This is completely against non-nullable principles we've always been used to. I think the default non-nullable string's default value should have been String.Empty.
Default values for variables are a basic feature of the language that is in C# since the very beginning. The specification defines the default values:
For a variable of a value_type, the default value is the same as the value computed by the value_type's default constructor ([see] Default constructors).
For a variable of a reference_type, the default value is null.
This makes sense from a practical standpoint, as one of the basic usages of defaults is when declaring a new array of values of a given type. Thanks to this definition, the runtime can just zero all the bits in the allocated array - default constructors for value types are always all-zero values in all fields and null is represented as an all-zero reference. That's literally the next line in the spec:
Initialization to default values is typically done by having the memory manager or garbage collector initialize memory to all-bits-zero before it is allocated for use. For this reason, it is convenient to use all-bits-zero to represent the null reference.
Now Nullable Reference Types feature (NRT) was released last year with C#8. The choice here is not "let's implement default values to be null in spite of NRT" but rather "let's not waste time and resources trying to completely rework how the default keyword works because we're introducing NRTs". NRTs are annotations for programmers, by design they have zero impact on the runtime.
I would argue that not being able to specify default values for reference types is a similar case as for not being able to define a parameterless constructor on a value type - runtime needs a fast all-zero default and null values are a reasonable default for reference types. Not all types will have a reasonable default value - what is a reasonable default for a TcpClient?
If you want your own custom default, implement a static Default method or property and document it so that the developers can use that as a default for that type. No need to change the fundamentals of the language.
I mean somewhere deep down in the implementation of C# it must be specified that 0 is the default value of an int. We also could have chosen 1 or 2 but no, the consensus is 0. So can't we just specify the default value of a string is String.Empty when the Nullable reference type feature is activated?
As I said, the deep down is that zeroing a range of memory is blazingly fast and convenient. There is no runtime component responsible for checking what the default of a given type is and repeating that value in an array when you create a new one, since that would be horribly inefficient.
Your proposal would basically mean that the runtime would have to somehow inspect the nullability metadata of strings at runtime and treat an all-zero non-nullable string value as an empty string. This would be a very involved change digging deep into the runtime just for this one special case of an empty string. It's much more cost-efficient to just use a static analyzer to warn you when you're assigning null instead of a sensible default to a non-nullable string. Fortunately we have such analyzer, namely the NRT feature, which consistently refuses to compile my classes that contain definitions like this:
string Foo { get; set; }
by issuing a warning and forcing me to change that to:
string Foo { get; set; } = "";
(I recommend turning on Treat Warnings As Errors by the way, but it's a matter of taste.)
Again this doesn't make sense to me, in my opinion the default value of a Foo should be new Foo() (or gives a compile error if no parameterless constructor is available). Why by default setting to null an object that isn't supposed to be null?
This would, among other things, render you unable to declare an array of a reference type without a default constructor. Most basic collections use an array as the underlying storage, including List<T>. And it would require you to allocate N default instances of a type whenever you make an array of size N, which is again, horribly inefficient. Also the constructor can have side effects. I'm not going to further ponder how many things this would break, suffice to say it's hardly an easy change to make. Considering how complicated NRT was to implement anyway (the NullableReferenceTypesTests.cs file in the Roslyn repo has ~130,000 lines of code alone), the cost-efficiency of introducing such a change is... not great.
The bang operator (!) and Nullable Value Types
The compiler is completely fine with the 1st line, no warning at all. I discovered null! recently when experiencing with the nullable reference type feature and I was expecting the compiler to be fine for the 2nd line too but this isn't the case. Now I'm just really confused as for why Microsoft decided to implement different behaviors.
The null value is valid only for reference types and nullable value types. Nullable types are, again, defined in the spec:
A nullable type can represent all values of its underlying type plus an additional null value. A nullable type is written T?, where T is the underlying type. This syntax is shorthand for System.Nullable<T>, and the two forms can be used interchangeably. (...) An instance of a nullable type T? has two public read-only properties:
A HasValue property of type bool
A Value property of type T
An instance for which HasValue is true is said to be non-null. A non-null instance contains a known value and Value returns that value.
The reason for which you can't assign a null to int is rather obvious - int is a value type that takes 32-bits and represents an integer. The null value is a special reference value that is machine-word sized and represents a location in memory. Assigning null to int has no sensible semantics. Nullable<T> exists specifically for the purpose of allowing null assignments to value types to represent "no value" scenarios. But note that doing
int? x = null;
is purely syntactic sugar. The all-zero value of Nullable<T> is the "no value" scenario, since it means that HasValue is false. There is no magic null value being assigned anywhere, it's the same as saying = default -- it just creates a new all-zero struct of the given type T and assigns it.
So again, the answer is -- no one deliberately tried to design this to work incompatibly with NRTs. Nullable value types are a much more fundamental feature of the language that works like this since its introduction in C#2. And the way you propose it to work doesn't translate to a sensible implementation - would you want all value types to be nullable? Then all of them would have to have the HasValue field that takes an additional byte and possible screws up padding (I think a language that represents ints as a 40-bit type and not 32 would be considered heretical :) ).
The bang operator is used specifically to tell the compiler "I know that I'm dereferencing a nullable/assigning null to a non-nullable, but I'm smarter than you and I know for a fact this is not going to break anything". It disables static analysis warnings. But it does not magically expand the underlying type to accommodate for a null value.
Summary
Considering it doesn't protect at all against having null in a non-nullable reference type variable, it seems this new feature doesn't change anything and doesn't improve developers' life at all (as opposed to non-nullable value types which could NOT be null and therefore don't need to be null-checked)
So at the end it seems the only value added is just in terms of signatures. Developers can now be explicit whether or not a method's return value could be null or not or if a property could be null or not (for example in a C# representation of a database table where NULL is an allowed value in a column).
From the official docs on NRTs:
This new feature provides significant benefits over the handling of reference variables in earlier versions of C# where the design intent can't be determined from the variable declaration. The compiler didn't provide safety against null reference exceptions for reference types (...) These warnings are emitted at compile time. The compiler doesn't add any null checks or other runtime constructs in a nullable context. At runtime, a nullable reference and a non-nullable reference are equivalent.
So you're right in that "the only value added is just in terms of signatures" and static analysis, which is the reason we have signatures in the first place. And that is not an improvement on developers' lives? Note that your line
string nonNullableString = default(string);
gives off a warning. If you did not ignore it (or even better, had Treat Warnings As Errors on) you'd get value - the compiler found a bug in your code for you.
Does it protect you from assigning null to a non-null reference type at runtime? No. Does it improve developers' lives? Thousand times yes. The power of the feature comes from warnings and nullable analysis done at compile time. If you ignore warnings issued by NRT, you're doing it at your own peril. The fact that you can ignore the compiler's helpful hand does not make it useless. After all you can just as well put your entire code in an unsafe context and program in C, doesn't mean that C# is useless since you can circumvent its safety guarantees.
Again this doesn't make sense to me, in my opinion the default value of a Foo should be new Foo() (or gives a compile error if no parameterless constructor is available)
That's an opinion, but: that isn't how it is implemented. default means null for reference-types, even if it is invalid according to nullability rules. The compiler spots this and warns you about it on the line Foo nonNullableFoo = default(Foo);:
Warning CS8600 Converting null literal or possible null value to non-nullable type.
As for string nonNullableString = null!; and
The compiler is completely fine with the 1st line, no warning at all.
You told it to ignore it; that's what the ! means. If you tell the compiler to not complain about something, it isn't valid to complain that it didn't complain.
So at the end it seems the only value added is just in terms of signatures.
No, it has lots more validity, but if you ignore the warnings that it does raise (CS8600 above), and if you suppress the other things that it does for you (!): yes, it will be less useful. So... don't do that?
I have a class containing a number of properties, something like:
public class Update
{
public int Quantity { get; set; }
public decimal Price { get; set; }
public string Name { get; set; }
}
Each instance of Update does not necessarily have each property set, and another part of the system needs to know which have been set, and which haven't.
One option I've got is to make all the value types Nullable and so a null value would represent the concept of not being set. Whilst this would work, I don't really like the idea of having some properties explicitly Nullable (the value types) and some nullable by virtue of being a reference type. The class definition would look ugly and I'm not convinced a null check is semantically the best approach.
I could create a class very similar to Nullable<T> that has no constraint on the T with an IsSet property. I prefer this option to using Nullable, but I'd still like to see if anyone has an alternative representation that's better than the options I've suggested.
You really should stick to the preexisting idiom here. Use the built-in nullability.
I see your concern with nullability being different for value and ref types. Your workaround would work. But it is just a cosmetic change which gains you litte. I recommend that you change yourself instead of changing the code in this case. Try to fit yourself to the existing conventions.
Edit: Sometimes you need to be able to make a value optional in generic code. In this case you need to use some custom option type. By from experience I can tell that this is pretty nasty to use. It is not my solution of choice.
The reference types are already nullable, effectively - if you use int?, decimal? and string then every one of your properties can be null.
The problem comes if you ever want to set the string value to a null reference - if null is effectively a valid value which is set.
You certainly could write a Maybe<T> type, but I'm not sure I would - I would probably just use null... aside from anything else, it'll be more familiar for others reading the code who are used to C# idioms. For all the "anti-null" sentiment around (which I do share in many situations) there are cases where it's the simplest approach.
I don't really like the idea of having some properties Nullable (the
value types) and some not (the reference types)
Reference types are obviously nullable.
string t = null; //is totally valid
I'd say that Nullable is exactly what you want to use for this purpose.
You could wrap the members with properties (as you already do) to have the class show normal values to the outside alongside with "is it set"-methods to check if needed. But on the inside I'd use Nullable.
To suggest you something new... If you just talking about one class like Update with a limited number of members I would use just IsSet.
But if you have i.e. a number of similar classes with this behavior or a lot of properties I could suggest you using t4 templates. You can, in example, get class properties (of needed type or attribute) as it described in this article and auto generate code based on the list of properties (implementing any design you want automatically)
I could describe it more if one is interested...
The solution here to
use nullable wherever null is not a valid option
use a default value wherever null is a valid option but you are perfectly sure that a given value won't occur
use a boolean flag for each property where null is a valid value and you can't name a default which won't be used ever.
Examples:
Quantity should be nullable, because if it is set its value won't be null ever
Name should be defaulted to "" if Name might be null (the lack of a name) and you are sure Name will never be ""
A flag, let's say nameSet should be used if Name might have a null value and you can't think of a default value. This flag would be false by default and when you first set the value of Name, the flag should be set to true too.
If you want to handle all your properties in the same way a solution would be to create a class which would contain an Object and a boolean flag. The Object would store the value of the property and the flag would store whether the property was initialized, but I don't like this, because it creates a boolean flag even if it's not needed.
I've been searching for some good guidance on this since the concept was introduced in .net 2.0.
Why would I ever want to use non-nullable data types in c#? (A better question is why wouldn't I choose nullable types by default, and only use non-nullable types when that explicitly makes sense.)
Is there a 'significant' performance hit to choosing a nullable data type over its non-nullable peer?
I much prefer to check my values against null instead of Guid.empty, string.empty, DateTime.MinValue,<= 0, etc, and to work with nullable types in general. And the only reason I don't choose nullable types more often is the itchy feeling in the back of my head that makes me feel like it's more than backwards compatibility that forces that extra '?' character to explicitly allow a null value.
Is there anybody out there that always (most always) chooses nullable types rather than non-nullable types?
Thanks for your time,
The reason why you shouldn't always use nullable types is that sometimes you're able to guarantee that a value will be initialized. And you should try to design your code so that this is the case as often as possible. If there is no way a value can possibly be uninitialized, then there is no reason why null should be a legal value for it. As a very simple example, consider this:
List<int> list = new List<int>()
int c = list.Count;
This is always valid. There is no possible way in which c could be uninitialized. If it was turned into an int?, you would effectively be telling readers of the code "this value might be null. Make sure to check before you use it". But we know that this can never happen, so why not expose this guarantee in the code?
You are absolutely right in cases where a value is optional. If we have a function that may or may not return a string, then return null. Don't return string.Empty(). Don't return "magic values".
But not all values are optional. And making everything optional makes the rest of your code far more complicated (it adds another code path that has to be handled).
If you can specifically guarantee that this value will always be valid, then why throw away this information? That's what you do by making it a nullable type. Now the value may or may not exist, and anyone using the value will have to handle both cases. But you know that only one of these cases is possible in the first place. So do users of your code a favor, and reflect this fact in your code. Any users of your code can then rely on the value being valid, and they only have to handle a single case rather than two.
Because it's inconvenient to always have to check whether the nullable type is null.
Obviously there are situations where a value is genuinely optional, and in those cases it makes sense to use a nullable type rather than magic numbers etc, but where possible I would try to avoid them.
// nice and simple, this will always work
int a = myInt;
// compiler won't let you do this
int b = myNullableInt;
// compiler allows these, but causes runtime error if myNullableInt is null
int c = (int)myNullableInt;
int d = myNullableInt.Value;
// instead you need to do something like these, cumbersome and less readable
int e = myNullableInt ?? defaultValue;
int f = myNullableInt.HasValue ? myNullableInt : GetValueFromSomewhere();
I think the language designers feel that 'reference types being nullable by default' was a mistake, and that non-nullable is the only sensible default, and you should have to opt into nullness. (This is how it is in many modern functional languages.) "null" is usually a heap of trouble.
You seem to have 2 different questions...
Why would I ever want to use non-nullable data types in C#?
Simple, because the value-type data you're relying on is guaranteed by the compiler to actually have a value!
Why wouldn't I choose nullable types by default, and only use non-nullable types when that explicitly makes sense?
As Joel has already mentioned, a type can only be null if it is a reference type. Value types are guaranteed by the compiler to have a value. If your program depends on a variable to have a value, then this is the behavior you will want by not choosing a nullable type.
Of course, when your data is coming from anywhere that is not your program, then all bets are off. The best example is from a database. Database fields can be null, so you would want your program variable to mimic this value - not just create a "magic" value (i.e. -1, 0, or whatever) that "represents" null. You do this with nullable types.
Although null values can be convenient for using as "not-initialized-yet" or "not-specified" values, they make the code more complex, mainly because you're overloading the meaning of null as well as the variable (number-or-null vs. just-a-number).
NULL values are favoured by many database designers and SQL database programmers but with a small change in thinking about the problem you can do away with null values and actually have simpler and more reliable code (e.g., no worrying about NullReferenceExceptions).
There's actually a large demand for a "T!" operator that makes any reference type non-nullable, similar to how "T?" makes value types nullable, and Anders Hejlsberg, the inventor of C#, wished he had included the ability.
See also the question, Why is “null” present in C# and java?
I tend to use Nullable types wherever they make sense -- I won't care about performance until it's a problem, then I'll fix the few areas where it is and be done with it.
However, I also find that in general, most of my values end up being non-nullable. In fact, there are many times I'd actually like a NotNullable I can use with reference types to find out about a null problem when I get the null, not later on when I try to use it.
The only time that a Nullable Type should ever be used, is in the case that a certain field in a table of the database absolutely requires that a null be sent or received by the application at some point. Even in such a case, one should always try to find a way around using the Nullable Type. Bool isn't always your best friend.