Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you define a generic that takes *any* nullable type, value or reference?

Playing with the new nullable reference types in C#. Glad to see they poached this from Swift! It's such a great feature! BUT... since it's essentially 'bolted-on' to the language, I'm struggling to create a generic that can take any nullable type whether value or reference, which is trivial in Swift.

Consider this class:

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

Here's what I'm trying to achieve, using Int and a class called 'Foo' as an example:

public class LabeledInt : LabeledValue<Int>{}

var myLabeledIntA = new LabeledInt(){
    label = "Forty Four",
    value = 44
}

var myLabeledIntB = new LabeledInt(){
    label = "Not Set",
    value = null
}

public class LabeledFoo : LabeledValue<Foo>{}

var myLabeledFooA = new LabeledFoo(){
    label = "Set",
    value = new Foo()
}

var myLabeledFooB = new LabeledFoo(){
    label = "Not Set",
    value = null
}

This complains that I have to define TValue as nullable. However I can't find a constraint that solves both nullable value types (i.e. Int?) and nullable reference types (i.e. Foo?). How would one write such a constraint?

These don't work...

public abstract class LabeledValue<TValue>
where TValue : Nullable {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue>
where TValue : struct {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue> {
    public string?          label { get; set; }
    public Nullable<TValue> value { get; set; }
}

Note, I also tried this thinking the nullability could just be passed in as the actual type parameter, but then it complains that 'Value' isn't set.

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue  value { get; set; }
}

public class LabeledInt : LabeledValue<Int?>{}
like image 704
Mark A. Donohoe Avatar asked Jan 14 '20 17:01

Mark A. Donohoe


People also ask

Can generic type be null?

Also, we cannot simply return null from a generic method like in normal method. Below is the error that a generic method will throw if we are trying to return null. So, to return a null or default value from a generic method we can make use default().

How do you define a nullable?

Nullable<int> i = null; A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. For example, Nullable<int> can be assigned any value from -2147483648 to 2147483647, or a null value. The Nullable types are instances of System.

Is nullable a reference type?

Nullable types represent value-type variables that can be assigned the value of null. You cannot create a nullable type based on a reference type. (Reference types already support the null value.) So, no they're not reference types.

What is a nullable reference?

Nullable reference types are a compile time feature. That means it's possible for callers to ignore warnings, intentionally use null as an argument to a method expecting a non nullable reference. Library authors should include runtime checks against null argument values.


1 Answers

Ok, found it. You have to use two new explicit attributes, AllowNull and MaybeNull.

Here's the revised code...

public abstract class LabeledValue<TValue> {

    public string? label { get; set; }

    [AllowNull, MaybeNull]
    public TValue value { get; set; }
}

With that change, I can now do all of the following...

public class LabeledInt  : LabeledValue<int>{}
public class LabeledNInt : LabeledValue<int?>{}
public class LabeledFoo  : LabeledValue<Foo>{}
public class LabeledNFoo : LabeledValue<Foo?>{}

And use them like this...

var a = new LabeledInt();
a.Value = 4;
a.value = null // This won't compile

var b = new LabeledNInt();
b.Value = 4;
b.Value = null; // This compiles just fine

var c = new LabeledFoo();
c.Value = new Foo();
c.Value = null; // This won't compile

var d = new LabeledNFoo();
d.Value = new Foo();
d.Value = null; // This compiles just fine

Note: There is still a warning about Value being uninitialized, but it's only a warning, not an error. You have to make sure to explicitly set Value for non-null types before accessing it. Kind of defeats the purpose of using nullable/non-nullable types, but this is more a hack than a true solution which isn't actually possible since nullable value types are really the concrete Nullable<T> whereas nullable reference types are regular reference types just adorned with an attribute to let the compiler know not to accept nulls.

like image 148
Mark A. Donohoe Avatar answered Oct 05 '22 03:10

Mark A. Donohoe