Thursday, November 08, 2007

Non-nullable reference types in C#

C# 1.0 has non-nullable value types (an int variable must always have a valid int value, it cannot be null) and nullable reference types (a string variable can be null). C# 2.0 added nullable value types (an int? variable - note the "?" - can be null). There are unfortunately no non-nullable reference types (a string variable which can never be null). Patrick Smacchia has a summary of a possible notation and also of the issues that could appear.

However, until Microsoft decides to add non-nullable reference types, there's a way of doing the same thing with a somewhat more complicated notation.

  1. using System;  
  2.   
  3. namespace Extensions  
  4. {  
  5.   public struct NotNull<T> where T : class  
  6.   {  
  7.     public readonly T value;  
  8.   
  9.     public NotNull(T arg)  
  10.     {  
  11.       if (arg == null)  
  12.         throw new ArgumentNullException("arg");  
  13.   
  14.       value = arg;  
  15.     }  
  16.   
  17.     // convert non-nullable to regular  
  18.     public static implicit operator T(NotNull<T> arg)  
  19.     {  
  20.       return arg.value;  
  21.     }  
  22.   
  23.     // convert regular to non-nullable  
  24.     public static implicit operator NotNull<T>(T arg)  
  25.     {  
  26.       return new NotNull<T>(arg);  
  27.     }  
  28.   
  29.     // explicit cast to non-nullable -- needed when T == object  
  30.     public static NotNull<object> Cast(object arg)  
  31.     {  
  32.       return new NotNull<object>(arg);  
  33.     }  
  34.   
  35.     public override string ToString()  
  36.     {  
  37.       return value.ToString();  
  38.     }  
  39.   
  40.     public override bool Equals(object obj)  
  41.     {  
  42.       return value.Equals(obj);  
  43.     }  
  44.   
  45.     public override int GetHashCode()  
  46.     {  
  47.       return value.GetHashCode();  
  48.     }  
  49.   
  50.     public static bool operator ==(NotNull<T> left, NotNull<T> right)  
  51.     {  
  52.       return Equals(left, right);  
  53.     }  
  54.   
  55.     public static bool operator !=(NotNull<T> left, NotNull<T> right)  
  56.     {  
  57.       return !Equals(left, right);  
  58.     }  
  59.   }  
  60. }  


A method enforcing a not-null argument would look like this:

  1. public string Test(NotNull<string> s)  
  2. {  
  3.   string ss = s;  
  4.   return ss.Substring(0); // no need to test for null  
  5. }  


and would be called like this:

  1. public void TestNotNull()  
  2. {  
  3.   string s;  
  4.   
  5.   s = "something";  
  6.   Test(s);  
  7.   s = null;  
  8.   Test(s);  
  9. }  


Incidentally, this notation also serves as a form of specification - this parameter should not be null. I was thinking of using attributes but I think it would lead to a more verbose notation in case of several non-nullable parameters. I hope this helps someone :)

5 comments:

Anonymous said...

Convert it to a struct at least.

otherwise you have allowed a direct pass of null:

public void TestNotNull()
{
string s;

s = "something";
Test(s);

s = null;
Test(s); <- Fails on enter

Test(null); <- Fails inside method.
}

using a struct would make it fail up front as intented.

Marcel said...

Thank you for your advice and my apologies for not replying earlier. I will have to test this, as it's not directly obvious to me why Test(null) only fails inside the method, but I will test it and correct if needed.

Marcel said...

I just realized that "null" is a valid NotNull<string> value. Thanks, I will change the code.

Anonymous said...

It's dont assertion.
[Test]
public void testOperatorBoolEquals()
{
NotNull string obj = "some text";
NotNull string obj2 = "some text";
Assert.IsTrue(obj == obj2);
}

it's will be work with this changes

public static bool operator ==(NotNull T left, NotNull T right)
{
return Equals(left.value, right.value);
}

public static bool operator !=(NotNull T left, NotNull T right)
{
return !Equals(left.value, right.value);
}

Tosh Afanasiev said...

Your implementation is open to this line of code:


Test( new NotNull<string>() );


You're right to use a readonly field to store the reference however you should make it private and return the reference through a property that (additionally) checks for null before returning.