Most developers know that in C#, we have mainly the two types : Reference type and Value type. But we have one more type, that is called a Nullable type.
Actually it is a value type but it has features of both Reference and Value type. It has the capability to hold a value or can have a null reference, in other words there is no value. Nullable types were introduced in .Net 2.0.
As we know, a value type is allocated space on a stack, and every value type must be initialized to some value when declared. If you don't provide it by yourself then C# does it for you. So every value type is assigned a value whether you initialize it or not.
The issue with value type is that it's always has some value whether we assign it or not. If the variable has initial value then we cannot say that this is default value or initialized in the code. To overcome this issue the Nullable type was introduced.
We can declare and initialize a Nullable type as in the following:
C# also provides a short hand syntax for this as:
Now let's see it in the
reflector, how it got implemented. Here you can see the Class declaration as:
- public struct Nullable<T> where T: struct
- {
- private bool hasValue;
- internal T value;
- public Nullable(T value);
- public bool HasValue { get; }
- public T Value { get; }
- public T GetValueOrDefault();
- public T GetValueOrDefault(T defaultValue);
- public override bool Equals(object other);
- public override int GetHashCode();
- public override string ToString();
- public static implicit operator T?(T value);
- public static explicit operator T(T? value);
- }
It shows
Nullable can be used over a struct type only, in other words a value type.
Here we will discuss specific properties of Nullable type that are relevant to this topic. Now let's go to the details of the method public Nullable(T value). This is a parametrized constructor. Now let's see what is inside this code:
- public Nullable(T value)
- {
- this.value = value;
- this.hasValue = true;
- }
Here you can see that this is assigning a value to the variable and setting
hasValue to true. Now let's see the
HasValue property.
- public bool HasValue
- {
- get
- {
- return this.hasValue;
- }
- }
So it actually has one variable
hasValue that does the entire job. By default it is set to false and once we assign a value to it, it is set to true. You can assign it null as well and
HasValue will return false. How does it behave if you write
- Nullable<int> t = new Nullable<int>()
Now when you access t it'll print nothing. Also
HasValue will be false.
Now let's see what public T Value { get; } does:
- public T Value
- {
- get
- {
- if (!this.HasValue)
- {
- ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
- }
- return this.value;
- }
- }
As you can see whether
HasValue is true or not. If false throws some exception else it return the value.
Also let's have a view of two more next methods public T GetValueOrDefault() and public T GetValueOrDefault(T defaultValue). As the method name suggests, that returns the existing value and if it is not set then the type's default value is returned. The definition is like this:
- public T GetValueOrDefault()
- {
- return this.value;
- }
-
- public T GetValueOrDefault(T defaultValue)
- {
- if (!this.HasValue)
- {
- return defaultValue;
- }
- return this.value;
- }
So you can see it checks the HasValue and based on this return the result. Similarly if you want to see a public override bool Equals(object other) method then it has the code like:
- public override bool Equals(object other)
- {
- if (!this.HasValue)
- {
- return (other == null);
- }
- if (other == null)
- {
- return false;
- }
- return this.value.Equals(other);
- }
This also checks the
HasValue before comparing it. So we have gone through the underlying implementation of
Nullable type.
Note - I have picked the underlying implementation using Reflector for better understanding.
There are two operator's method you can see at last. You all must be knowing this is Operator Overloading. But what is this for? Let's see the code below:
- int i = 5;
-
- Nullable<int> t = i;
Actually here I have declared and initialized an int variable and created a
Nullable type and assigned the int variable to it. This is actually accomplished using the operators.
So it means, we can assign a value type to a Nullable type that underlying type is the same as an underlying type of a Nullable type without any casting.
But reverse is not possible. You need to do an explicit cast as:
- Nullable<int> t = 5;
-
- int i = (int)t;
But you always it write as:
- Nullable<int> t = 5;
-
- int i = t.Value;
Let's discuss some more scenarios.
First scenario
- int? a=8;
- object o = a;
- long d = (long)o;
It will be compiled successfully but will throw and an Invalid Cast Exception. In other words, you cannot cast it to any other type except the underlying type that is here int although int type can be held by long.
But the following lines will run successfully:
- long d = (int)o;
- long? d = (int)o;
Because here we are converting to an underlying type int that we can assign to long as it is a widening conversion.
Now let's move to another point.
Second scenario
Guess what will be the output of the following lines:
- int? b=5;
- Console.WriteLine(b.GetType());
Is it System.Nullable<Int32>. No
This actually shows System.Int32. So beware.
Few more things
We can use a Unary operator over Nullable type. It will be null if it has no value or null else applies the operator.
Binary operators also can be used with a
Nullable type. Give it a view:
- int? b=5;
- int? a = 7;
- Console.WriteLine(a+b);
This will print 12. But what if:
- int? b=5;
- int? a=null;
- Console.WriteLine(a+b);
This will print nothing because the addition result will be null. So be sure, during any binary operation if one operand (or does not have any value) is null. The result will also be null (or would not have any value).
I hope you have enjoyed the article.