Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Nullable<T> nullable? Why it cannot be reproduced?

Tags:

c#

nullable

When I write

Nullable<Nullable<DateTime>> test = null;

I get a compilation error:

The type 'System.Datetime?' must be a non-nullable value type in order to use it as a paramreter 'T' in the generic type or method 'System.Nullable<T>'

But Nullable<T> is a struct so it's supposed to be non-nullable.

So I tried to create this struct:

public struct Foo<T> where T : struct
{
    private T value;

    public Foo(T value)
    {
        this.value = value;
    }

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

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

Now when I write

        Nullable<Foo<DateTime>> test1 = null;
        Foo<Nullable<DateTime>> test2 = null;
        Foo<DateTime> test3 = null;

The first line is ok but for the second and third lines I get the two following compilation error:

The type 'System.DateTime?' must be a non-nullable value type in order to use it as a parameter 'T' in the generic type or method 'MyProject.Foo<T>' (second line only)

and

Cannot convert null to 'MyProject.Foo<System.DateTime?> because it is a non-nullable value type'

        Foo<Nullable<DateTime>> test = new Foo<DateTime?>();

doesn't work neither event if Nullable<DateTime> is a struct.

Conceptually, I can understand why Nullable<T> is nullable, it avoids having stuffs like DateTime?????????? however I can still have List<List<List<List<List<DateTime>>>>>...

So why this limitation and why can't I reproduce this behavior in Foo<T>? Is this limitation enforced by the compiler or is it intrinsic in Nullable<T> code?

I read this question but it just says that it is not possible none of the answers say fundamentally why it's not possible.

like image 501
Guillaume Avatar asked Sep 18 '12 11:09

Guillaume


3 Answers

But Nullable is a struct so it's supposed to be non-nullable.

Nullable<T> is indeed a struct, but the precise meaning of the generic struct constraint as stated in the docs is:

The type argument must be a value type. Any value type except Nullable can be specified. See Using Nullable Types (C# Programming Guide) for more information.

For the same reason, your line

Foo<Nullable<DateTime>> test2 = null;

results in the compiler error you are seeing, because your generic struct constraint restricts your generic T argument in a way so Nullable<DateTime> must not be specified as an actual argument.

A rationale for this may have been to make calls such as

Nullable<Nullable<DateTime>> test = null;

less ambiguous: Does that mean you want to set test.HasValue to false, or do you actually want to set test.HasValue to true and test.Value.HasValue to false? With the given restriction to non-nullable type arguments, this confusion does not occur.

Lastly, the null assignment works with Nullable<T> because - as implied by the selected answers and their comments to this SO question and this SO question - the Nullable<T> type is supported by some compiler magic.

like image 154
O. R. Mapper Avatar answered Nov 14 '22 23:11

O. R. Mapper


The error is saying that the type-parameter of Nullable should be not-nullable.

What you're doing, is creating a Nullable type which has a nullable type-parameter, which is not allowed:

Nullable<Nullable<DateTime>>

is the same as

Nullable<DateTime?>

Which is quite pointless. Why do you want to have a nullable type for a type that is already nullable ?

Nullable is just a type that has been introduced in .NET 2.0 so that you are able to use ' nullable value types'. For instance, if you have a method wich has a datetime-parameter that is optional; instead of passing a 'magic value' like DateTime.MinValue, you can now pass null to that method if you do not want to use that parameter.

like image 38
Frederik Gheysels Avatar answered Nov 14 '22 23:11

Frederik Gheysels


In generic classes where T: struct means that type T cannot be null. However Nullable types are designed to add nullability to structs. Technically they are structs, but they behave like they may contain null value. Because of this ambiguity the use of nullables is not allowed with where T: struct constraint - see Constraints on Type Parameters

Nullable types are not just generic structs with special C# compiler support. Nullable types are supported by CLR itself (see CLR via C# by Jeffrey Richter), and looks like this special CLR support makes them non-recursive.

  • CLR supports special boxing/unboxing rules int? i = 1; object o = i will put int value into variable o and not Nullable<int> value. In case of multiple nullables - should o = (int??)1; contain int or int? value?
  • CLR has special support for calling GetType and interface members - it calls methods of underlying type. This actually leads to situation when Nullable.GetType() throws a NullObjectReference exception, when it has NullValueFlag.

As for C#, there are a lot of features in C# that are hard-coded for nullable types. Based on this article Nullable Types (C# Programming Guide) the primary goal of introducing nullable types is to add null support for the types that do not support nulls. Logically, since DateTime? already supports nulls it shouldn't be allowed to be "more" nullable.

This document also plainly states that

Nested nullable types are not allowed. The following line will not compile: Nullable<Nullable<int>> n;

Special C# features of nullable types:

  • C# has special ?? operator. Should (int???)null ?? (int)1 resolve to (int??)1 or to (int)1 value?
  • Nullables have special System.Nullable.GetValueOrDefault property. What should it return for nested nullables?
  • Special processing for ? == null and ? != null operators. If the Nullable<Nullable<T>> contains Nullable<T> value, but this value is null, what should HasValue property return? What should be the result of comparison with null?
  • Special implicit conversions. Should int?? i = 10 be implicitly convertible?
  • Explicit conversions. Should be int i = (int??)10; supported?
  • Special support for bool? type Using nullable types. E.g. (bool?)null | (bool?)true == true.

So, should CLR support recursive GetType() call? Should it remove Nullable wrappers when boxing value? If it should do for one-level values, why don't for all other levels as well? Too many options to consider, too many recursive processing.

The easiest solution is to make Nullable<Nullable<T>> non-compilable.

like image 39
Artemix Avatar answered Nov 14 '22 23:11

Artemix