Home > Blockchain >  How do I tell the compiler about my nullable c# generic constraints?
How do I tell the compiler about my nullable c# generic constraints?

Time:03-29

I've got an editor that lets the user edit the "simple" properties of objects (int, string, DateTime, etc.), so it iterates through the properties and then constructs for each simple property an object to support the editing:

#nullable enable

public class DataNode<T>
{
   protected PropertyInfo Prop;
   protected object Source;
   protected T Value;
   protected bool Modified;

   public DataNode(PropertyInfo prop, object source)
   {
      Prop = prop;
      Source = source;
      object? value = prop.GetValue(source);
      if (value is T t)
         Value = t;
      else
         Value = default;
      Modified = false;
   }

   public virtual bool IsValid()
   {
      return true;
   }

   public void Save()
   {
      if (Modified)
      {
         Prop.SetValue(Source, Value);
         Modified = false;
      }
   }
}

I then have subclasses for specific properties - for example, if there's an int property where I don't want the user to be able to enter a negative value, I can create a specific subclass:

public class NonNegativeIntNode : DataNode<int>
{
   public NonNegativeIntNode (PropertyInfo prop, object source)
   : base(prop, source)
   {
   }

   public override bool IsValid()
   {
      return Value >= 0;
   }
}

So that all works fine, and before you ask:

  1. The selection of which DataNode class to use is controlled by custom attributes on the properties
  2. The DataNode does more than this that I've left out for simplicity. For example the BoolDataNode class knows that to edit this value it should use a checkbox

The problem is that the compiler is grumbling that "Value = default;" is a possible null reference assignment, and that the DataNode constructor might be exiting with a null value for 'Value'.

I can keep the compiler happy by defining Value as "protected T? Value" but that adds unnecessary complication elsewhere to check for a null value when I know darn well that inside BoolDataNode that Value will never be null.

I tried splitting DataNode into two classes - NullableDataNode and NonNullableDataNode - but inside NonNullableDataNode, even though I specify "where T: notnull", the compiler is still worried about 'default'

It seems like saying "where T: notnull" means "I am never going to set Value to null" where what I want to tell the compiler is "T is a type that cannot be null" (like bool)

Is there a way to reassure the compiler that all is well, without simply turning off all the nullability warnings with pragmas?

CodePudding user response:

I can keep the compiler happy by defining Value as "protected T? Value" but that adds unnecessary complication elsewhere to check for a null value when I know darn well that inside BoolDataNode that Value will never be null.

The right thing to do is to make this a protected T? Value field, since if T is a reference type, you'll be assigning null. People accessing your Value field need to know that if T is a non-nullable reference type, they might still be getting a null out.

You're confused about the meaning of T? when T is unconstrained however. T? means that the value is "defaultable", not "nullable". That's a subtle difference, but means that you can assign a value of default(T). Another way of putting that is:

If T is a reference type, T? means that you can assign null. If T is a value type, the ? in T? effectively has no meaning.

In other words, DataNode<string>.Value is of type string?, but DataNode<bool>.Value is of type bool, not bool?.

(Technically, there's no way to have T?, when T is unconstrained and is a value type, mean Nullable<T>. For nullable value types, the compiler outputs a member of type Nullable<T> rather than T. However, generics are expanded by the runtime rather than the compiler.)


Note that this changes if T is constrained to be a value type: in that case, T? suddenly starts meaning Nullable<T>.

  • Related