C Basic Syntax: Empty Type Details

  • 2021-06-28 13:47:56
  • OfStack

Here is System.Nullable < T > Definition in FCL.


[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Nullable<T> where T :struct
{
 private Boolean hasValue= false;
 internal T value= default(T);

public Nullable(T value)
 {
this.value= value;
this.hasValue= true;
 }

public Boolean HasValue {get {return hasValue; } }

public T Value
 {
get
  {
  if (!hasValue)
    {
   throw new InvalidOperationException("Nullable object must have a value.");
    }
   return value;
  }
 }

public T GetValueOrDefault() {return value; }
public T GetValueOrDefault(T defaultValue)
 {
if(!HasValue)return defaultValue;
return value;
 }

public override Boolean Equals(object other)
 {
if(!HasValue)return (other== null);
if(other== null)return false;
return value.Equals(other);
 }

public override int GetHashCode()
 {
if(!HasValue)return 0;
return value.GetHashCode();
 }

public override string ToString()
 {
if(!HasValue)return "";
return value.ToString();
 }

public static implicit operator Nullable<T>(T value)
 {
return new Nullable<T>(value);
 }
}

You can see that each instance of the null type has two common read-only properties:

1.HasValue

HasValue is of type bool.When a variable contains a non-null value, it is set to true.

2.Value

The type of Value is the same as the base type.If HasValue is true, then Value contains meaningful values.If HasValue is false, accessing Value raises InvalidOperationException.

So how do we define nullable types?

The type of null can be declared in one of two ways:


System.Nullable<T> variable - or - T? variable

T is a base type that can be an null type.T can be any value type including struct;It cannot be a reference type.

Now for an example, use 1 to see if the effect is the same.


Console.WriteLine("======== Empty Type Operation Demo ========\n");
      Console.WriteLine("\n=========Nullable<T>===========\n");
      Nullable<int> x = 5;
      Nullable<bool> y = false;
      Nullable<double> z = 5.20;
      Nullable<char> n = null;
      Console.WriteLine("x.HasValue={0},   x.Value={1}",x.HasValue,x.Value);
      Console.WriteLine("y.HasValue={0},   y.Value={1}", y.HasValue, y.Value);
      Console.WriteLine("z.HasValue={0},   z.Value={1}", z.HasValue, z.Value);
      Console.WriteLine("n.HasValue={0},   n.Value={1}",n.HasValue, n.GetValueOrDefault());
      Console.WriteLine("\n============== T? ============\n");
      int? X = 5;
      bool? Y = false;
      double? Z = 5.20;
      char? N = null;
      int?[] arr ={1,2,3,4,5};//1 Array of nullable types 
      Console.WriteLine("X.HasValue={0},   X.Value={1}", X.HasValue,X.Value);
      Console.WriteLine("y.HasValue={0},   Y.Value={1}", Y.HasValue, Y.Value);
      Console.WriteLine("Z.HasValue={0},   Z.Value={1}", Z.HasValue, Z.Value);
      Console.WriteLine("N.HasValue={0},   N.Value={1}", N.HasValue, N.GetValueOrDefault());
      Console.WriteLine("\n================================\n");
      Console.ReadKey();

Nullable types can be cast to regular types either explicitly by using a cast or by using the Value property.The conversion from a normal type to a type that can be null is implicit.For example:


int? a = 5;//int--->int?
double? b = a; //int?---->double?
int? c = (int?)b;//double?---int?
int d = (int)c;//int?---->int  You cannot implicitly convert, for example int d=c; It cannot be compiled
int? e = null;
int f = e.Value;// Can be compiled but will prompt exception throws InvalidOperationException

Nullable types can also use predefined unary and binary operators (promotion operators), as well as any existing user-defined value type operators.If the operand is null, these operators will produce an null value;Otherwise, the operator will use the contained values to calculate the result.For example:


int? a = 10;
int? b = null;
//1 Meta operator( + ++  -- = - ! ~)
a++;        //a=11;
//2 Meta operator( + - * / % & | ^ << >>) 
a *= 10;   //a=110;
//a = a + b;  //now a is null
// Equality Operator (== !=) 
if (b == null)
{
    b=b.GetValueOrDefault();
}
Console.WriteLine(a.Value);
a = a + b;
/*  if(a == null) ...  
* if(b == null) ...  
* if(a != b) ... */
// Comparison operator  
if (a > b)
{
    Console.WriteLine("a>b");
}

Here is a summary of how C#uses operators:
1.1-ary operator (++ - --!~).If the operand is null, the result is null.

2.2-ary operator (+ - * /%| ^) < < > > ).Either of the two operands is null, resulting in null.

3. Equality operator (==!=).If both operands are null, they are equal.If one operand is null, they are not equal.If neither operand is null, compare the values to see if they are equal.

4. Comparison operator ( < > < = > =).Either of the two operands is null, resulting in false.If neither operand is null, the values are compared.

So far, in my explanation of a=a+b for the code above, it is actually equivalent to:


a = a.HasValue && b.HasValue ? a.Value + b.Value : (int?)null;

When you manipulate an empty instance, you generate a lot of code, such as the following:


privatestaticint? NullableCodeSize(int? a, int? b)
{
    return a + b;
}

When compiling this method, the IL code generated by the compiler is equivalent to the following C#code:

privatestatic Nullable<int> NullableCodeSize(Nullable<int> a, Nullable<int> b)
{
Nullable<int> nullable1 = a;
Nullable<int> nullable2 = b;
if(!(nullable1.HasValue & nullable2.HasValue))
returnnew Nullable<int>();
else
returnnew Nullable<int>(nullable1.GetValueOrDefault() + nullable2.GetValueOrDefault());
}

? ?operation

Returns the value of the left operand if it is not null.If the operand on the left is null, the value of the operand on the right is returned.Empty join operators make it easy to set default values for variables.One benefit of the null join operator is that it can be used for both reference types and nullable types.As follows:


//=========== Nullable Type =========
int? b =null;
int a = b ??520;
Equivalent to: //a = b.HasValue ? b.Value : 520
Console.WriteLine(x); //print:"520"
//=========== reference type =========
String s = GetstringValue();
String s= s ??"Unspecified";

Equivalent to:

//String s = GetstringValue();
//filename = (s != null) ? s : "Unspecified";

Packing and Unpacking Conversion

Assume there is an Nullable < int >Variable, which is logically set to null.If you pass this variable to a method that expects an object, you must box the variable, and you will box the Nullable < int > 1 reference to the method.This is not an ideal result, because the method is now passed as a non-empty value, even if Nullable < int > The variable logically contains the null value.To solve this problem, CLR executes a special code when boxing an empty variable to maintain the legal status of the empty type on the surface.

Specifically, when CLR vs. an Nullable < T > When an instance is boxed, it checks to see if it is null.If so, CLR does not actually do any boxing and returns an null value.If the nullable instance is not null, CLR takes the value from the nullable instance and boxes it.That is, an Nullable with a value of 5 < int > One packed Int32 with a value of 5 will be packed.As follows:


// Yes Nullable<T> Pack or return null Or go back 1 Boxed T
int? n =null;
object o = n; //o by null
Console.WriteLine("o is null={0}", o ==null); //"true" n =5;
o = n; //o Quote 1 Boxed int
Console.WriteLine("o's type={0}", o.GetType()); //"System.Int32"

CLR allows one boxed value type T to be unboxed to one T or one Nullable < T > .If the reference to the boxed value type is null and you want to unbox it as Nullable < T > Then CLR will make Nullable < T > The value is set to null.The following code demonstrates this behavior:


// Establish 1 Boxed int
object o =5; // Unpack it as 1 individual Nullable<int> and 1 individual int
int? a = (int?)o; //a=5
int b = (int)o; //b=5 // Create Initialized As null Of 1 References
o =null; // "Unbox" it as 1 individual Nullable<int> and 1 individual int
a = (int?)o; //a=null;
b = (int)0; // Throw NullReferenceException

CLR may have to allocate memory when unpacking a value type as an empty version of a value type.This is a very special behavior because in all other cases, unboxing never results in memory allocation.Because a boxed value type cannot be simply unpacked as an empty version of a value type, and does not contain an hasValue field in the boxed value type, CLR must assign an Nullable when unpacking < T > Object to initialize hasValue = true, value = value type value.
Call interface methods

In the following code, there will be an Nullable < int > Type variable n transformed to an IComparable < int > That is, an interface type.However, Nullable < T > Unlike int, IComparable is not implemented < int > Interface.The C#compiler allows such code to be compiled, and the CLR validator considers such code verifiable, allowing us to use this simpler syntax:


int? n =5;
int result = ((IComparable)n).CompareTo(5); // Compile and run smoothly
Console.WriteLine(result);

If CLR does not provide this special support, then the unboxed value type must be transformed before it can be transformed into an interface to make calls:

int result = ((IComparable)(int)n).CompareTo(5);

You can use the C# typeof operator to create an Type object that represents a type that can be null:


System.Type type = typeof(int?);

You can also use classes and methods from the System.Reflection namespace to generate Type objects that represent types that can be null.However, if you try to use the GetType method or the is operator to get type information at run time that can be a type variable for null, the result is an Type object that represents the underlying type, not the type itself for null.

If GetType is called on a type that can be null, the boxing operation is performed when the type is implicitly converted to Object.Therefore, GetType always returns an Type object representing the underlying type rather than a type that can be null.


Related articles: