I suggest you ...

Add non-nullable reference types in C#

Non-nullability checks have to be manually encoded hundreds of times in any large real-world project, and they are not compile-time-enforced. There are code contracts in .Net 4.0, but their usage is still very verbose, and only partly compile-time-enforced.

What I wish is a pendant to the null-lifting operator ?, for instance, !, so that one could write:
void MyMethod(string! s){ /* s cannot be null :) */}

Or, the way ReSharper does it:
void MyMethod([NotNull] string s){ /* s cannot be null :) */}

5,784 votes
Vote
Sign in
Check!
(thinking…)
Reset
or sign in with
  • facebook
  • google
    Password icon
    Signed in as (Sign out)
    You have left! (?) (thinking…)
    Marc Sigrist shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

    98 comments

    Sign in
    Check!
    (thinking…)
    Reset
    or sign in with
    • facebook
    • google
      Password icon
      Signed in as (Sign out)
      Submitting...
      • Joel Greijer commented  ·   ·  Flag as inappropriate

        Wow this would be really great. I've written so much unneccessary code to check for null in strings and classes. And many of those crazy unneccessary nullref-exceptions would be lost

      • gzak commented  ·   ·  Flag as inappropriate

        @Vladislav

        "With the non-nullable strings, the first case (you are sure that string is not null) will be explicitly expressed by the former string having static type string! instead... so you'll just need to propagate the new type, switching from implicit logical condition in your program (some variables here and there may not be null) to explicit ones"

        Exactly.
        But I would argue that this is actually a fairly expensive propagation.
        Consider:

        void Foo(string s) { MyMethod(A(s) + B(s) + C(s) + s); }

        Where A, B, and C all accept and return plain string values, not string! values. Does this compile? If so, all the null-checks are runtime checks. If it doesn't compile, how do you fix it? And imagine if you don't own A, B, and C. Now what?

        The thing is, you have to start with the notion of non-nullable types and grow from there. But if you've already grown a large code base without them, it's impractical to introduce them later. That's what's missing here...

      • Anonymous commented  ·   ·  Flag as inappropriate

        @gzak: well, I won't point to the Spec# language which is able to enforce nun-nullable semantics, I'll point to a well-known example: C++. The C++'s references are basically the same as non-nullable references. So if C++ is able to cope with such a feature, why C# cannot be?

        Next, consider your example of MyMethod accepting string!. This has an equivalent right now, without non-nullable string: the documentation for the method may say that the string argument must be non-null. As a good citizen, you must either be sure that the string which you have is non-null, or you must check it before passing as argument. (You have a 3rd option of forgetting to check and getting a weird runtime exception though.)

        With the non-nullable strings, the first case (you are sure that string is not null) will be explicitly expressed by the former string having static type string! instead. The second case with runtime check will be unchanged: now you need it not for satisfying the requirement, but for converting string into a string!. (The third case is however not possible, which is a clear advantage.)

        Note that each string literal is itself a string!, so you'll just need to propagate the new type, switching from implicit logical condition in your program (some variables here and there may not be null) to explicit ones. The operations on strings and system functions known to return a non-null string can be automatically un-lifted by the compiler.

        Next, about solving the problem with division. Basically you are right in noticing that problem of invalid operations may be solved with a more strict type system. However introducing a non-zero int seems for me too big change for too small gain, and that is the only reason it's not suggested elsewhere.

        P.S.: I've already given my vote for safe navigation operator.

      • gzak commented  ·   ·  Flag as inappropriate

        Fundamentally, you can't enforce this kind of thing entirely at compile time in the same way you can't entirely catch division-by-zero errors at compile time (at least, not pleasantly).

        An important thing to notice here is that while it looks like you've solved the nullability problem for MyMethod, you've actually just pushed it up to all of its callers.

        Suppose a caller has a regular string, not a string!, can they pass it to MyMethod? If so, that's automatically a runtime check.

        Or do they in turn also need a string! first? But now the caller has to change. And god forbid the caller's string in turn came from one of its arguments, or from the result of some other method call, suddenly it becomes exponentially difficult for the caller of MyMethod to secure a string! to pass in... That would be the only way to 100% enforce this at compile time.

        And if there's a "converter" from string to string!, that converter is again a runtime check, automatically.

        Basically, to enforce this entirely at compile time you'd need to exponentially ripple the string! change all the way up the various call stacks (and sometimes down some other branches). It would be similar with trying to catch division-by-zero, where you can imagine something like this:

        int MyDivider(int y, int~ x) { /* int~ means non-zero int, so x cannot be 0 */ }

        So while I very much like the idea of non-nullability in theory, in practice it wouldn't really work, especially without breaking compatibility with a ton of existing code. It would really only work if you were designing a language from scratch, one which only has value types. But C# isn't that language, if you'd like that you should use F# instead.

        That's why I'm more in favor of introducing a ?. operator. It's comparatively simple, and doesn't break anything. Less is more...

        http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-

      • Jimmy commented  ·   ·  Flag as inappropriate

        then you have (string.IsNullOrEmpty(myString) ? "It's null, shoot!" : "Its not null, YES!");

        or (myString.Equals(null) ? "It's null, shoot!" : "Its not null, YES!");

        sorry just putting my 2 cents in...

      • Dan Walker commented  ·   ·  Flag as inappropriate

        1. I'd like to see this in VB as well.

        2. This kind of breaks the language spec of both C# and VB.
        Perhaps something akin to VB's Option keyword would do.. like Option NonNullable or Option Nullability Off. That would instruct VB/C# that a new syntax engine should be used. These new syntaxes would have to address the concerns people have already mentioned (eg. initialization, etc). [To be clear, I'm suggesting this be implemented as a syntax change, not through some hokey data type concept]

      • Kris Vandermotten commented  ·   ·  Flag as inappropriate

        Turns out Spec# has pretty good support for non-null types. It even supports arrays of non-null types, though I personnaly do not like the way in which it does so.

        To be precise, I very much like a lot of what I read in section 1.0 of the tutorial (http://specsharp.codeplex.com/wikipage?title=Tutorial), but not what I read in section 4.1.

        BTW, note that quite a bit of what Spec# had to offer can be done in C# today with code contracts (http://research.microsoft.com/en-us/projects/contracts/default.aspx).

      • Anonymous commented  ·   ·  Flag as inappropriate

        @Kris: you can see it like the feature in C++, where you can have arrays of (and use dynamic_cast with) pointers, but not references [which bear some resemblance to nullable and non-nullable reference types in C#].

      • Anonymous commented  ·   ·  Flag as inappropriate

        @Kris: the question boils down to implementation of default(T) for a non-nullable T.

        My personal opinion would be making it just illegal, so it wouldn't be possible to have an array of C!'s. So, as a direct answer to your question, this shouldn't be compilable.

        This would make an unpleasant difference between "natively" non-nullable value types and nullable reference types, however I don't see any better solution.

        Interesting is, how is this implemented in Comega (http://research.microsoft.com/en-us/um/cambridge/projects/comega/)?

      • Kris Vandermotten commented  ·   ·  Flag as inappropriate

        Dean G, your NonNullable<T> value type is, unfortunately, very nullable.

        For example:

        NonNullable<string> s = default(NonNullable<string>);

        Or

        NonNullable<string> s = (new NonNullable<string>[10])[0];

      • Kris Vandermotten commented  ·   ·  Flag as inappropriate

        I would vote for this, if someone can tell me what the value of v should be after executing this code, given class C { }:

        C![] items = new C![10];

        C! v = items[0];

        Or the value of v2, given class D : C { }:

        D! v2 = v as D!

      • Peter commented  ·   ·  Flag as inappropriate

        This would be by far my #1 feature request for C# / .NET. It would eliminate so many headaches and so much work. I don't know what is the best way to solve this problem, but if the .NET team would be willing to sacrifice backwards compatibility in the name of fixing this problem, I think it would instill a feeling that the platform is moving forward and willing to fix mistakes and not just accumulate them.

        So guys, please fix this and make it such a high priority that you would be willing to break things to save effort in the future.

        Whatever feature you come up with in the future, it would pale in comparison to the benefits of having nulls eliminated by the compiler. Thanks.

      • Dean G commented  ·   ·  Flag as inappropriate

        It is a great idea. Not sure where I got this code from, but this is what I've had to do in the mean time:

        ///<summary>A simple structure to wrap a class reference after ensuring that the reference is not null.</summary>
        public struct NonNullable<T> where T : new()
        {
        /// <summary>Check and wrap a value.</summary>
        /// <param name = "aItem">The value to check and wrap.</param>
        /// <exception cref="System.ArgumentNullException">If Value is a null reference.</exception>
        /// <remarks>To pass a non null value use ?? operator.</remarks>
        public NonNullable(T aItem)
        {
        if (aItem == null)
        {
        mValue = new T();
        // throw (new System.ArgumentNullException("value", "That value is null"));
        }
        else
        {
        mValue = aItem;
        }
        //return;
        }

        ///<summary>Get a String representation of the wrapped value.</summary>
        ///<returns>The result of the wrapped value's ToString().</returns>
        public override string ToString()
        {
        return (this.Value.ToString());
        }

        ///<summary>Implicit wrapping of the value.</summary>
        ///<returns>The wrapped value.</returns>
        ///<exception cref="System.ArgumentNullException">If Value is a null reference.</exception>
        public static implicit operator NonNullable<T>(T aItem)
        {
        return (new NonNullable<T>(aItem));
        }

        ///<summary>Implicit unwrapping of the value.</summary>
        ///<returns>The unwrapped value.</returns>
        public static implicit operator T(NonNullable<T> aItem)
        {
        return (aItem.Value);
        }

        /// <summary>The wrapped value.</summary>
        public T Value
        {
        get { return mValue; }
        private set { mValue = value; }
        }
        T mValue;
        }

      • Marc Sigrist commented  ·   ·  Flag as inappropriate

        @Vladislav: Yes, non-nullable reference types would make C# more "symmetric" (with nullable value types), which is in itself a good thing from a language design perspective.

      • Anonymous commented  ·   ·  Flag as inappropriate
      • Anonymous commented  ·   ·  Flag as inappropriate

        @Marc: Well, I don't think this brings too much of additional complexity into the language. On the contrary, it makes the type system simpler: with your suggestion, both reference and value types can be either nullable or not. As of now, only value types have a choice.

      • Marc Sigrist commented  ·   ·  Flag as inappropriate

        As my suggestion has become somewhat popular, I would like to add more context to it. Null references were introduced in ALGOL in 1965. The language designer has famously described this as his "billion dollar mistake" (see
        http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare).

        The error has been copied by the mainstream OO languages, inclusign C#. By now, the worldwide costs of preventing and fixing NullReferenceExceptions might easily be in the range of a billion dollars a year.

        Given the seriousness of the problem, the correct thing to do would be to redesign C# (or even the .Net Framework) so that, by default, the usage of null is forbidden. To indicate that something might or might not exist, a new "Option<T>" or "Maybe<T>" type could be added, ideally with proper syntactical support in the language. An example of a .Net language who works like this is F#.

        However, I don't think it is realistic to expect such a fundamental change in C#, a long-established mainstream language. Therefore, I have suggested the second-best solution, which is to add non-nullable reference types. Of course, this would make C# a bit more complicated, as we would then have four "contingency idioms" (nullable vs. non-nullable value types vs. reference types). But it would still be less complex and expensive than the combined effort of ReSharper/CodeRush, code contracts, coding conventions, documentation, and testing,
        while still running the constant risk of NullReferenceExceptions.

      Feedback and Knowledge Base