Search code examples
c#.netnullable

What Is The Point of Value on Nullable Types In C#


Trying to get a better understanding of why this is a language feature:

We have:

public static DateTime? Date { get; set; }


static void Main(string[] args)
{
    Date = new DateTime(2017, 5, 5);

    Console.WriteLine(Date.Value.Date);
    Console.Read();
}

Why do I need to use Value to take the value from the nullable type? It's not like it checks for null before calling Date, if the value is null it will throw a NullReference exception. I get why .HasValue can work,

but am unsure about why we need .Value on each nulllable type?


Solution

  • This is due to how nullable types are implemented.

    The questionmark syntax only translates into Nullable<T>, which is a struct you could very well write yourself (except for the …? syntax being a language feature for this type).

    The .NET Core implementation of Nullable<T> is open source and its code helps explaining this.

    Nullable<T> only has a boolean field and a value field of the underlying type and just throws an exception when accessing .Value:

    public readonly struct Nullable<T> where T : struct
    {
        private readonly bool hasValue; // Do not rename (binary serialization)
        internal readonly T value; // Do not rename (binary serialization)
    
        …
    
        public T Value
        {
            get
            {
                if (!hasValue)
                {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
                }
                return value;
            }
        }
    …
    

    When you do a cast / assignment like DateTime aDateTime = (DateTime)nullableDateTime, then you only call an operator that is defined on the same class which works exactly like an operator defined on a custom type. This operator only calls .Value so the cast hides the access to the property:

        public static explicit operator T(Nullable<T> value)
        {
            return value.Value;
        }
    

    There also is an operator for the inverse assignment, so DateTime? nullableNow = DateTime.Now would call:

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