SoFunction
Updated on 2025-03-07

Detailed explanation of the usage of Nullable type Nullable

1. Introduction

As we all know, value type variables cannot be null, which is why they are called value types. However, in the actual development process, the value is also requirednullSome scenes of For example, the following scenario:

Scenario 1: You retrieve nullable integer data columns from the database table, thenullThere is no way to assign this value to the Int32 type in C#;

Scenario 2: You bind attributes in the UI, but fields of certain value types are not required to be entered (such as the date of death in personnel management);

Scene 3: In Java,is a reference type, so fields of this type can be set tonull. However, in CLR,It is a value type, and the DateTime variable cannotnull. If an application written in Java wants to communicate date/time to a web service running on the CLR, if the Java application sends isnull, there is no corresponding type in CLR;

Scenario 4: When passing a value type in a function, if the value of the parameter cannot be provided and you do not want to be passed, the default value can be used. But sometimes the default value is not the best choice, because the default value actually passes a default parameter value, and the logic requires special processing;

Scenario 5: When deserializing data from xml or json, the value of a certain value type attribute is missing in the data source, which is inconvenient to deal with.

Of course, there are many similar situations in our daily work.

To get rid of these situations, Microsoft has added the concept of nullable value types in CLR. To understand this more clearly, let's take a look<T>Logical definition of type:

 namespace System
 {
     [Serializable]
     public struct Nullable<T> where T : struct 
     {
         private bool hasValue;
         internal T value;
  
         public Nullable(T value) {
              = value; 
              = true;
         }
 
         public bool HasValue { 
             get {
                 return hasValue; 
             } 
         }
  
         public T Value { 
             get {
                 if (!HasValue) { 
                     (ExceptionResource.InvalidOperation_NoValue); 
                 }
                 return value; 
             }
         }
 
         public T GetValueOrDefault() { 
             return value;
         } 
 
         public T GetValueOrDefault(T defaultValue) {
             return HasValue ? value : defaultValue;
         } 
 
         public override bool Equals(object other) { 
             if (!HasValue) return other == null; 
             if (other == null) return false;
             return (other); 
         }
 
         public override int GetHashCode() {
             return HasValue ? () : 0; 
         }
  
         public override string ToString() { 
             return HasValue ? () : "";
         } 
 
         public static implicit operator Nullable<T>(T value) {
             return new Nullable<T>(value);
         } 
 
         public static explicit operator T(Nullable<T> value) { 
             return ; 
         }
     }
 }

From the above definition, we can summarize the following points:

  • Nullable<T> type is also a value type;
  • The Nullable<T> type contains a Value property to represent the underlying value, and also includes aBooleanThe HasValue property of type is used to indicate whether the value isnull ;
  • Nullable<T> is a lightweight value type. The size of memory occupied by an instance of type Nullable<T> is equal to a value type and aBooleanThe sum of memory sizes of type occupies;
  • The generic parameter T of Nullable<T> must be of value type. You can only use Nullable<T> type with value type, and you can also use user-defined value types.

2. Syntax and usage

Using the Nullable<T> type, just specify a generic parameter T of other value types.

Example:

    Nullable&lt;int&gt; i = 1;
    Nullable&lt;int&gt; j = null;
    Nullable&lt;Nullable&lt;int&gt;&gt; k; //This is an error syntax,Compilation will report an error。

CLR also provides a way to abbreviate it.

     int? i = 1;
     int? j = null;

The value of the underlying type can be obtained through the Value property. As shown below, if notnull, the actual value will be returned, otherwise it will be thrownInvalidOperationExceptionException; you can check whether it isnull

     Nullable&lt;int&gt; i = 1;
     Nullable&lt;int&gt; j = null;
 
     ();
     //Output result: True 
     ();
     //Output result: 1 
     ();
     //Output result: False 
     ();
     //Throw an exception: 

3. Type conversion and operation

C# also supports simple syntax to use the Nullable<T> type. It also supports implicit conversions and conversions of Nullable<T> instances. The following example demonstrates:

     // Implicit conversion from System.Int32 to Nullable<Int32>     int? i = 5;
 
     // Implicit conversion from 'null' to Nullable<Int32>     int? j = null;
 
     // Explicit conversion from Nullable<Int32> to Int32     int k = (int)i;
 
     // Conversion between basic types     Double? x = 5; // Implicit conversion from Int to Nullable<Double>     Double? y = j; // fromNullable&lt;Int32&gt; Implicit conversionNullable&lt;Double&gt;

Use operators for Nullable<T> types, the same way to use the underlying types included.

  • unary operators (++, --, - etc.), if the value of type Nullable<T> isnullWhen, returnnull
  • Any operand of binary operators (+, -, *, /, %, ^, etc.) isnull,returnnull
  • For the == operator, if both operands arenull, the expression is evaluated astrue, if any operand isnull, the expression evaluates to false; if neither isnull, it is compared as usual.
  • For relational operators (>, <, >=, <=), if any operand isnull, then the result of the operation isfalse, if the operands are notnull, then compare the value.

See the following example:

     int? i = 5;
     int? j = null;
 
     // unary operator     i++; // i = 6 
     j = -j; // j = null
 
     // Binary operator     i = i + 3; // i = 9 
     j = j * 3; // j = null;
 
     // Equal sign operator (==, !=)     var r = i == null; //r = false
     r = j == null; //r = true
     r = i != j; //r = true
 
     // Comparison operators (<, >, <=, >=)     r = i &gt; j; //r = false
 
     i = null;
     r = i &gt;= j; //r = false,Notice,i=null、j=null,but&gt;=The result returned isfalse

Nullable<T> can also support ternary operators like reference types.

     // If the employee's age returns null (the date of birth may not be entered), set the value 0.     int age =  ?? 0;
 
     // Use ternary operators in aggregate functions.     int?[] numbers = {};
     int total = () ?? 0;

4. Packing and unboxing

We already know that Nullable<T> is a value type, and now let’s talk about its boxing and unboxing.

CLR adopts a special rule to handle Nullable<T> type packing and unboxing. When an instance of type Nullable<T> is boxed, the CLR checks the instance's HasValue property: If it istrue, the value of the instance Value property is boxed and the result is returned; iffalse, then return directlynull, no treatment is done.

When unboxing, it is reversed from the boxing. CLR will check whether the unboxed object isnull, if it is to create a new instance directly new Nullable<T>(), if it is notnull, unbox the object into type T, and then create a new instance new Nullable<T>(t).

     int? n = null;
     object o = n; //No boxing operation will be performed, and the null value will be returned directly 
     ("o is null = {0}", (o, null));
     //Output result: o is null = True 
 
     n = 5;
     o = n; //o Reference a boxed Int32 
     ("o's type = {0}", ());
     //Output result: o's type = System.Int32 
     o = 5;
 
     //Unbox the Int32 type into Nullable<Int32> type     int? a = (Int32?)o; // a = 5 
     //Unbox the Int32 type into Int32 type     int b = (Int32)o; // b = 5
 
     // Create an initialized to null     o = null;
     // Change null to Nullable<Int32> type     a = (Int32?)o; // a = null 
     b = (Int32)o; // throw an exception:NullReferenceException

5. GetType() method

When calling Nullable<T> typeGetType()When a method is used, the CLR actually returns the type of the generic parameter. Therefore, you may not be able to distinguish whether a Nullable<Int32> type is an Int32 type or a Nullable<Int32> on an instance. See the following example:

     int? i = 10;
     (());
     //The output result is: System.Int32     
     i = null;
     (()); //NullReferenceException

Cause analysis:

This is because the callGetType()When the method is used, the current instance has been boxed. According to the contents of the previous part of the boxing and unboxing, the Int32 type is actually called here.GetType()method.

Call value typeGetType()When the method is used, packing will be generated. You can verify this yourself.

6. ToString() method

When calling Nullable<T> typeToString()When the method, if the value of the HasValue property isfalse, then return, if the value of the property istrue, then the logic of the call is(). See the following example:

     int? i = 10;
     (());
     //Output result: 10 
     i = null;
     (() == );
     //Output result:True

7. Help

Microsoft also provides a same nameThe static class includes three methods:

 public static class Nullable
 {
     //Return the basic type parameter of the specified nullable type.     public static Type GetUnderlyingType(Type nullableType);
 
     //Compare two relative values ​​<T> objects.     public static int Compare&lt;T&gt;(T? n1, T? n2) where T : struct
 
     //Indicate whether the two specified <T> objects are equal.     public static bool Equals&lt;T&gt;(T? n1, T? n2) where T : struct
 }

Let's focus on thisGetUnderlyingType(Type nullableType)Methods, the other two methods are used to compare values, you can study them yourself.

GetUnderlyingType(Type nullableType)The method is used to return a base type of nullable type, ifnullableTypeThe parameter is not a closed Nullable<T> generic, then reversenull。 

     ((typeof(Nullable&lt;int&gt;)));
     //Output result: System.Int32 
     ((typeof(Nullable&lt;&gt;)) == null);
     //Output result: True 
     ((typeof(int)) == null);
     //Output result: True 
     ((typeof(string)) == null);
     //Output result:True

8. Syntactic sugar

Microsoft provides rich syntax sugar for Nullable<T> to reduce the workload of developers, and the following is what I think of for your reference.

Abbreviation

    int? i = 5;

    int? j = null;

    var r = i != null;

    var v = (int) i;

    i++;

    i = i + 3;

    r = i != j;

    r = i >= j;

    var k = i + j;

    double? x = 5;

    double? y = j;

Compiled statements

    int? i = new int?(5);

    int? j = new int?();

    var r = ;

    var v = ;

    i =  ? new int?(() + 1) : new int?();

    i =  ? new int?(() + 3) : new int?();

    r = () != () ||  != ;

    r = () >= () &&  & ;

    int? k =  &  ? new int?(() + ()) : new int?();

    double? x = new double?((double) 5);

    double? y =  ? new double?((double) ()) : new double?();

This is all about this article about the detailed explanation of the usage of the nullable type Nullable<T>. I hope it will be helpful to everyone's learning and I hope everyone will support me more.