4

After writing this question I found out that Nullable<T> has a type constraint of struct on T so you cannot actually use Nullable for reference types. However, some good answers interpreted the questions as 'if you could' or 'if you would write your own Nullable or NonNullable type'. This interpretation has lead to some nice answers so I've decided to not close or delete the question.

I know the nullable construct in C# was introduced to support null for value types. However, the language does not seem to have a problem with using nullable on other types. I wonder if this is a bad practice. I've come up with some arguments for both sides, but I can't decide.

Pros

  • Foo? x makes it exceptionally clear that a variable can be null.

Cons

  • If Foo is not a value type we create a struct with a reference type which is a bad practice.
  • Why wrap a variable that can already be null with more boilerplate code. Comments or naming can already indicate that the value can be null.
  • Not marking the object as nullable does not guarantee that it will not be null. So inconsistent use will lead to confusion.
  • Performance overhead? (Extremely neligible in most cases).
  • It makes the code slightly harder to read

With nullable

Foo? x;

// <snip>

x = null;

// <snip>

if(x.HasValue) { x.Value.Bar(); }

Without

Foo x; // Might be null

// <snip>

x = null;

// snip>

if(x != null) { x.Bar(); }
  • 7
    "the language does not seem to have a problem with using nullable on other types" Nullable<T> has a struct constrain on T, so you only use it on value types. – CodesInChaos Jun 2 '15 at 9:55
  • @CodesInChaos I'll be damned. I'm 99% sure I just wrote some code using Nullable<SomeClass> that compiled. But of course, it no longer does now. Please close this question. :/ – Roy T. Jun 2 '15 at 10:08
  • 2
    I don't think the question should be closed. Aside the (not so obvious) mistake related to the type constraints in the definition of Nullable<T>, the question can take a form of “What if this was possible?”, which is perfectly valid and answerable. – Arseni Mourzenko Jun 2 '15 at 10:51
  • 1
    @MainMa I'll edit the question. Given the nice answer by Ben Aaronson it indeed seems valid. – Roy T. Jun 3 '15 at 12:29
  • 1
    If this were a Jeopardy question, the answer would be "What is the Null Object Pattern?" – radarbob Jun 3 '15 at 16:12
9

As others have said, this isn't possible, but there are a couple of easy constructs you can create yourself that achieve similar aims.

Maybe<T>

public struct Maybe<T> where T : class
{
   public bool HasValue { get; private set; }

   private readonly T _value;
   public T Value
   {
       get
       {
           if(!HasValue){throw new InvalidOperationException();}
           return _value;
       }
   }

   public Maybe(T value) : this()
   {
       if(value==null) { throw new ArgumentNullException("value"); }
       HasValue = true;
       _value = value;
   }
}

(This could also be implemented other ways, such as an IEnumerable<T> with the constraint that it has exactly 0 or 1 items)

NotNull<T>

public class NotNull<T> where T : class
{
    public readonly T Value;

    public NotNull(T value)
    {
        if(item == null){ throw new ArgumentNullException("value"); }
        Value = value;
    }
}

Maybe<T> essentially just reimplements Nullable<T> for reference types. It has similar advantages and disadvantages to the ones described in your post. It's probably only valuable if you have a convention throughout your project to use it for any value where null is a valid value.

NotNull<T> is likely to be more directly what you want, as MainMa stated. By passing around values wrapped in this type you can remove the need to check whether a value is null in a guard clause. However, there's no way to completely prevent a NotNull<T> from itself being null. You can't make it a struct because of the requirement of a default constructor, which would leave Value null.

(EDIT: As others have pointed out, in C# 6 you will be able to have a parameterless constructor for a struct, but it will not be used when initializing arrays or getting the result of default(T), so you still can't really rely on the guarantee that Value won't be null)

So while both of these potentially add some value in signalling whether null should be considered valid or not, they're both limited in that they require constant adherence to a convention, in addition to the boilerplate and readability issues. Whether the trade-off is worth it would be a matter of judgement, but remember that code contracts or other static analysis tools also exist as a possible alternative way of addressing this issue.

  • Just to future proof this answer. I think that in the upcoming C# 6 you can create default constructors for structs. – Roy T. Jun 2 '15 at 12:18
  • @RoyT. Very interesting! Is there more full documentation of this feature? A quick googling gives Jon Skeet suggesting here: stackoverflow.com/questions/333829/… that array initialization might not call the default constructor. From that I'd guess it's also possible that default(T) might not. – Ben Aaronson Jun 2 '15 at 12:20
  • @BenAaronson: stackoverflow.com/questions/29105561/… and directly from the horses mouth blogs.msdn.com/b/csharpfaq/archive/2014/11/20/… – jmoreno Jun 2 '15 at 12:51
  • @jmoreno Thanks. I'd say the limitation with default(T) and array initialization is enough reason to consider a struct NotNull<T> "dangerous". To me, the idea that a NotNull<T> might be null is a much simpler consideration than having to say it "almost but not quite guarantees" that the value will be non-null. – Ben Aaronson Jun 2 '15 at 13:11
  • See also Jon Skeet's NonNullable<T> class inside of miscutil. It has a several features a production implementation of NonNullable should include (equality Operators, GetHashCode, and implicit conversions). I recommend any implementation of Maybe have these same features. Skeet does use a struct, though this also doesn't prevent a null NonNullable. – Brian Jun 3 '15 at 13:34
5

Nullable<T> has a constraint which won't let you use it with classes, but only structs.

If T wasn't constrained, using Nullable<T> for classes would be a mistake anyway. Your intent is to simplify your code, but you're actually making it harder.

In C#, classes are always nullable. By telling that the nullable entity is nullable, you're not really making your code more explicit for the maintainers, since the purpose of such construct is all but clear. It's like if I were documenting that a method which accepts int as a parameter should take a value between int.MinValue and int.MaxValue. Does it make the code clearer? Not really, because it is just redundant with the language rules.

Usually, what C# developers look for is the opposite thing: how to specify that something cannot be null while being a class. For example, how do you tell that public Product CreateProduct(string name, Price price) can't have a null name, nor a null price and cannot return a null?

Code contracts is one way to go, but are obviously limited to pre-conditions, post-conditions and invariants. If you need to globally specify that Price can never ever be a null because it doesn't make sense in a context of your business logic, then you might end up moving Price from a class to a struct.

  • 2
    If Nullable<T> was possible for all T, then you could have a convention for your project that all parameters where null is a valid value must be passed as explicitly Nullable. This would mean that any other variable would be known to be non-nullable. This kind of convention wouldn't be enforceable by the compiler though, so whether or not it would actually be a good idea is arguable. – Ben Aaronson Jun 2 '15 at 11:12
  • For a type like Nullable<T> to really work for all T, it would be necessary to do without the fiction that an empty Nullable<T> has anything to do with null. A MayHaveValue<T> empty or not, should box as its own type, but all boxed instances of all types of the form T, MayHaveValue<T>, MayHaveValue<MayHaveValue<T>> etc. should be unboxable to any other such type based on the same T. If the system included some CLR magic to allow T to be covariant, such a type would greatly enhance the TryGetValue pattern. Too late now, though. – supercat Jun 4 '15 at 17:25
  • @EsbenSkovPedersen: thank you for noting that. Indeed, I was not talking about classes in general, but in a context of C# where each class' object can be null. I edited my answer to be more explicit on this point. – Arseni Mourzenko Aug 4 '15 at 12:59
  • @MainMa I know but objects are not really nullable by nature. – Esben Skov Pedersen Aug 4 '15 at 13:00
2

This is not a question of good or bad pratices.

Nullable (the type that is behind the type?notation) does not allow reference types:

public struct Nullable where T : struct

It's really that easy. Only value types can be nullable using the Nullable<T> type.

2

I think it would be absolutely amazing and at least some members on the Roslyn team seems to think it is a good idea.

The proposed modification to the language is the ! operator and a change to the ? which could be used for reference types which could be used like.

string! a;//can never be null
string  b;//no change
string? c;//can explicitly be null

The beauty of this solution is there is no change to the generated IL so interop problems should be minimum. There should be some way to mark an assembly as safe which means it can't use the classical references, only ! and ?

0

The key to making this work is to make the property Value private, accessible only through a monadic Bind operation or an Extract method that supplies a default value of interest. This avoids the possibility of a null value ever being exposed.

public struct MaybeX<TValue> : IEquatable<MaybeX<TValue>> where TValue:class {
  /// <summary>TODO</summary>
  public static readonly MaybeX<TValue>   Nothing  = new MaybeX<TValue>(null); 

  ///<summary>Create a new MaybeX&lt;T>.</summary>
  public MaybeX(TValue value) : this() { _value = value; }

  ///<summary>The monadic Bind operation of type T to type MaybeX&lt;TResult>.</summary>
  [Pure]
  public  MaybeX<TResult>             Bind<TResult>(Func<TValue, MaybeX<TResult>> selector)
  where TResult:class {
      return! HasValue  ?  MaybeX<TResult>.Nothing  :  selector(Value);
  }

  ///<summary>Extract value of the MaybeX&lt;T>, substituting 
  ///<paramref name="defaultValue"/> as needed.</summary>
  [Pure]
  public  TValue                      Extract(TValue defaultValue) {
      return ! HasValue  ?  defaultValue  :  Value;
  }

  ///<summary>Wraps a T as a MaybeX&lt;T>.</summary>
  [Pure]
  public static implicit operator     MaybeX<TValue>(TValue value) {
      return value == null  ?  MaybeX<TValue>.Nothing  :  new MaybeX<TValue>(value);
  }

  ///<summary>Returns whether this MaybeX&lt;T> has a value.</summary>
  public   bool    HasValue { [Pure]get {return Value != null;} }

  ///<summary>If this MaybeX&lt;T> has a value, returns it.</summary>
  internal TValue  Value    { [Pure]get {return _value;} }  readonly TValue _value;
}

By then extending the base class with LINQ-compatible extension methods like this

[Pure]
public static class MaybeX {
  [Pure]
  public static MaybeX<TResult>           Select<T, TResult>(
      this MaybeX<T> @this,
      Func<T, TResult> projector
  ) where T : class where TResult : class {
      return ! @this.HasValue ? MaybeX<TResult>.Nothing
                              : projector(@this.Value).ToMaybeX();
  }
  [Pure]
  public static MaybeX<TResult>           SelectMany<TValue, TResult>(
      this MaybeX<TValue> @this,
      Func<TValue, MaybeX<TResult>> selector
  ) where TValue : class where TResult : class {
     return @this.Bind(selector);
  }
  [Pure]
  public static MaybeX<TResult>           SelectMany<TValue, T, TResult>(
      this MaybeX<TValue> @this,
      Func<TValue, MaybeX<T>> selector,
      Func<TValue, T, TResult> projector
  ) where T : class where TValue : class where TResult : class {
      return ! @this.HasValue ? MaybeX<TResult>.Nothing
                              : selector(@this.Value).Map(e => projector(@this.Value,e));
  }
}

the LINQ Comprehension syntax becomes available also. The above actually makes the Value property internal instead of private in order to allow the LINQ aliases to be implemented in place more efficiently; this assumes that the monads and their LINQ extension methods are alone in their assembly.

In my own library I annotate this class completely with Code Contracts like this

[Pure]
public  MaybeX<TResult>         Bind<TResult>(
    Func<TValue, MaybeX<TResult>> selector
) where TResult:class {
    selector.ContractedNotNull("selector");
    Contract.Ensures(Contract.Result<MaybeX<TResult>>() != null);

    MaybeX<TResult>.Nothing.AssumeInvariant();
    var result = ! HasValue ? MaybeX<TResult>.Nothing
                            : selector(Value);

    Contract.Assume(result != null);              // struct's never null
    //result.AssumeInvariant();                   // TODO - Why is this inadequate?
    return result;
}

and use Code Contracts to statically prove the absence of any escaping null values.

This small class of extension methods makes writing many of the code contracts much simpler and more effective:

  /// <summary>Extension methods to enhance Code Contracts and integration with Code Analysis.</summary>
  [Pure]
  public static class ContractExtensions {
#if RUNTIME_NULL_CHECKS
    /// <summary>Throws <c>ArgumentNullException{name}</c> if <c>value</c> is null.</summary>
    /// <param name="value">Value to be tested.</param>
    /// <param name="name">Name of the parameter being tested, for use in the exception thrown.</param>
    [ContractArgumentValidator]  // Requires Assemble Mode = Custom Parameter Validation
    public static void ContractedNotNull<T>([ValidatedNotNull]this T value, string name) where T : class {
      if (value == null) throw new ArgumentNullException(name);
      Contract.EndContractBlock();
    }
#else
    /// <summary>Throws <c>ContractException{name}</c> if <c>value</c> is null.</summary>
    /// <param name="value">Value to be tested.</param>
    /// <param name="name">Name of the parameter being tested, for use in the exception thrown.</param>
    [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value")]
    [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name")]
    [ContractAbbreviator] // Requires Assemble Mode = Standard Contract Requires
    public static void ContractedNotNull<T>([ValidatedNotNull]this T value, string name) {
        Contract.Requires(value != null, name);
    }
#endif

    /// <summary>Decorator for an object which is to have it's object invariants assumed.</summary>
    [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "t")]
    public static void AssumeInvariant<T>(this T t) { }

    /// <summary>Asserts the 'truth' of the logical implication 
    <paramref name="condition"/> => <paramref name="contract"/>.</summary>
    public static bool Implies(this bool condition, bool contract) {
        Contract.Ensures((! condition || contract)  ==  Contract.Result<bool>() );
        return ! condition || contract;
    }

    /// <summary>Returns true exactly if lower &lt;= value &lt; lower+height</summary>
    /// <param name="value">Vlaue being tested.</param>
    /// <param name="lower">Inclusive lower bound for the range.</param>
    /// <param name="height">Height of the range.</param>
    public static bool InRange(this int value, int lower, int height) {
        Contract.Ensures( (lower <= value && value < lower+height)  ==  Contract.Result<bool>() );
        return lower <= value && value < lower+height;
    }
  }

  /// <summary>Decorator for an incoming parameter that is contractually enforced as NotNull.</summary>
  [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
  public sealed class ValidatedNotNullAttribute : global::System.Attribute {}

protected by Community Aug 4 '15 at 8:17

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.