2

I know a string in C# can be 'nullable' by just using null.

However, the whole point of nullable types is that I get help from the compiler [edit : aka, type error : can't add apple to bananas]

And using this 'type system hack-ish' of 'nullability depends on the underlying type' breaks whatever guarantee I might have (which is no small feat as that seems to be the whole point of using a type system in the first place..)

What are the standard way to deal with this in C#, if one wants to ? Shall I just roll my own 'nullable' class ?

Edit

Let me rephrase the question :

What is the standard way, in C#, to make sure that a variable that you annotated as nullable, does not get assigned to a variable that you did not annotate as nullable.

That is : what is the standard way to have for all types, precisely what the ? keyword gives you for value type.

nicolas
  • 9,549
  • 3
  • 39
  • 83
  • 11
    I don't understand your question. – Soner Gönül Feb 19 '13 at 16:23
  • What specific problem are you encountering? – Graham Feb 19 '13 at 16:27
  • The question is : how to have an explicitely nullable type, that is, one that the ompiler is aware of, so that I have a way to not assign by mistake a 'non nullable' string to a nullable string. – nicolas Feb 19 '13 at 16:38
  • @SonerGönül do you understand nullable types ? – nicolas Feb 19 '13 at 16:44
  • @nicolas Do you? Or, more appropriate, do you actually know what you want to be able to do that you currently don't, because we have no idea what you're asking for based on your question. Perhaps you could provide a code sample of what isn't working so that we could help you address its problems. – Servy Feb 19 '13 at 16:45
  • @leppie because this is one of the annoyance that one does not get in F#. So I imagine most f# programmer have been confronted to this. – nicolas Feb 19 '13 at 16:47
  • @Servy As said, I try to have the nullable annotation for every type. so if you don't understand, please refer to nullable types. – nicolas Feb 19 '13 at 16:50
  • not that it is terribly hard to just roll your own, but I was thinking people had given some thoughts to the pros and cons of different options I might not see. – nicolas Feb 19 '13 at 16:52
  • @nicolas The nullable annotation is only value for types that are not already nullable, namely structs. There is no way for you to apply the annotation to a type that is already a nullable type. It is *not* possible for you to roll your own nullable type; at least to the point that `Nullable` works. Multiple aspects of it's functionality require compiler support, so your own `Nullable` can't ever be as feature rich. – Servy Feb 19 '13 at 16:54
  • @nicolas: This question is likely to confuse C# programmers with no F# experience, because the two languages address nullability in very different ways. If the question is re-opened I'll be glad to post an answer. – Daniel Feb 19 '13 at 16:56
  • @Daniel At hand, I have a bunch of DBNull.Value, null, empty strings, nullables, all defining nullity in the context of this c# program. quite interesting to work with ! – nicolas Feb 19 '13 at 17:19
  • @Daniel: The question has been re-opened. I would be interested in your answer, because I don't think it is possible to achieve this. I posted an answer myself explaining my thoughts. – Daniel Hilgarth Feb 19 '13 at 17:29

4 Answers4

4

If you are asking about a way to ensure that the return type of a certain method is not null, there is a solution: That method has to return a value type. Every method that returns a reference type can potentially return null. There is nothing you can do about it.

So, you could create a struct like this to be able to return a value type:

public struct SurelyNotNull<T>
{
    private readonly T _value;

    public SurelyNotNull(T value)
    {
        if(value == null)
            throw new ArgumentNullException("value");
        _value = value;
    }

    public T Value
    {
        get { return _value; }
    }
}

Your method that is supposed to return a string now can return SurelyNotNull<string>.

The problem with this approach is:
It doesn't work. While the return value of the method is guaranteed to be not null, the return value of SurelyNotNull<T>.Value is not. At first glance, it looks like it is guaranteed to be not null. But it can be, because of this:
Every struct has an implicit, public, parameterless constructor, even when another constructor is defined.
The following code is valid and compiles with the struct from above:

new SurelyNotNull<string>();

Conclusion:
In C#, you are unable to achieve what you are trying to do.
You can still use this approach, you just need to understand that someone could use this type and still produce a null value. To fail fast in this scenario, it would be a good idea to add a check to the getter of Value and throw an exception if _value is null.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • You could do the null check in the get as well. This way that evil null that shouldn't be there would not be proliferated through a system and would be caught the first time it's touched. – Timothy Shields Feb 19 '13 at 17:32
  • Interesting. With a bit of convention in the code, that'll do I guess. Although not bullet proof, this sure is an improvement over what there currently is (a mix of many things), and I can leverage the compiler to spread the non-nullity. – nicolas Feb 19 '13 at 17:33
  • @TimothyShields: Sure. But that still would be a runtime exception and not an error the compiler can spot. And that's what this is all about. – Daniel Hilgarth Feb 19 '13 at 17:33
  • @nicolas: The compiler still can't prove that `Value` won't be `null`, so when you are using static code checkers like ReSharper or Code Contracts you would still receive a whole bunch of "Possible usage of null" messages. – Daniel Hilgarth Feb 19 '13 at 17:35
  • I wouldn't say it's all about compile time. Yes it's nice to have compile time checks, but it can be a big improvement at runtime to catch that accidental null early on, rather than 3 minutes later when you go to use it. It can really help with debugging. – Timothy Shields Feb 19 '13 at 17:35
  • @DanielHilgarth of course. I mean the compiler would transmit the type (nullableof T .. and not T !), and I'd 'guarantee' with my eyeball with the hindsight that I applied the convention everywhere. – nicolas Feb 19 '13 at 17:38
  • @TimothyShields: I agree, fail fast is important. But compile time is even faster than runtime. So, if you can have a compile time error that's preferred over a runtime exception, no matter how early that might occur. – Daniel Hilgarth Feb 19 '13 at 17:38
  • Obviously I wasn't trying to make the statement that run-time error checking is better than compile-time error checking... The statement was simply that there could be value added by adding the null check in the `SurelyNotNull.Value` getter as well. – Timothy Shields Feb 19 '13 at 17:57
2

As Daniel Hilgarth pointed out, there's no bullet-proof way to achieve this. My proposal is similar to his, but with some added safety. You can decide for yourself if the benefits outweigh the cost of using a wrapper type throughout your program.

struct NonNull<T> where T : class {
    private readonly T _value;
    private readonly bool _isSafe;

    public NonNull(T value) {
        if (value == null)
            throw new ArgumentNullException();
        _value = value;
        _isSafe = true;
    }
    public T Value {
        get {
            if (_isSafe) return _value;
            throw new ArgumentNullException();
        }
    }
    public static implicit operator T(NonNull<T> nonNull) {
        return nonNull.Value;
    }
}

static class NonNull {
    public static NonNull<T> Create<T>(T value) where T : class {
        return new NonNull<T>(value);
    }
}

The wrapper type is primarily to make your intent self-documenting, so it's unlikely you would bypass it with a zero-initialized struct, but it keeps a flag to indicate it was initialized correctly anyway. In that admittedly unusual case it will throw an ArgumentNullException when accessing the value.

class Program {
    static void Main(string[] args) {
        IsEmptyString(NonNull.Create("abc")); //false
        IsEmptyString(NonNull.Create("")); //true
        IsEmptyString(null); //won't compile
        IsEmptyString(NonNull.Create<string>(null)); //ArgumentNullException 
        IsEmptyString(new NonNull<string>()); //bypassing, still ArgumentNullException
    }

    static bool IsEmptyString(NonNull<string> s) {
        return StringComparer.Ordinal.Equals(s, "");
    }
}

Now, is this better than an occasional NRE? Maybe. It can save a lot of boilerplate arg checking. You'll need to decide if it's worthwhile for your situation. Short of compiler support, like F# provides, there's no way to provide compile-time null safety, but you can (arguably) ease run-time safety.

You may want to up-vote this issue on Microsoft's customer feedback site: Add non-nullable reference types in C#

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • interesting ! reassure me : you had that in the backburner for a longtime right ? – nicolas Feb 19 '13 at 17:40
  • I haven't used this in production code, but mostly because I've made peace with this shortcoming in C# (and many other languages). F# took a huge leap forward by addressing this [billion dollar mistake](http://en.wikipedia.org/wiki/Tony_Hoare#Quotations), even if its attempt was limited by the need for interoperability. Still, you can create something of a null-safe walled garden for most of your F# code. – Daniel Feb 19 '13 at 17:47
  • Daniel, I hoped you had something else in mind when you posted your comment on the question. I really think that `null` is one of the bigger problems in current languages. About your code: Your implicit operator from `T` to `NonNull` removes *a lot* of the compile time type safety, because it allows `IsEmptyString(null)` to compile. I think it would be better to not provide it. – Daniel Hilgarth Feb 19 '13 at 17:47
  • You're right. I was trying to make it easy to use and tripped over myself. I've removed it. – Daniel Feb 19 '13 at 17:56
  • @DanielHilgarth: Sorry to disappoint. I had the same basic idea in mind. You might want to up-vote a related issue on User Voice (link is in my answer). – Daniel Feb 19 '13 at 18:02
  • would that be bad to add code for value type to have a uniform type ** private static bool IsValueType(S obj) { return typeof(S).IsValueType; } public NonNull(T value) { if (!IsValueType(value) && value == null) throw new ArgumentNullException(); _value = value; _isSafe = true; }** – nicolas Feb 19 '13 at 18:04
  • `value` can't be a `ValueType` due to the constraint `where T : class`. Plus, this issue only applies to reference types. – Daniel Feb 19 '13 at 18:06
  • @Daniel : the person who wrote the quote you mention is 10 meters away. grrrrr .... – nicolas Feb 19 '13 at 18:07
  • Well, since he wrote quicksort you can give him mercy. – Daniel Feb 19 '13 at 18:09
  • Doesn't this just trade one runtime exception for another? Can someone please highlight the advantage here? – Jonathan Wilson Oct 29 '14 at 18:13
2

You want something like a NotNull attribute. Using these will give you compile time warnings and errors and in some cases IDE feedback about assigning NULL values to NotNull attributes.

See this question :C#: How to Implement and use a NotNull and CanBeNull attribute

Community
  • 1
  • 1
Jason Hernandez
  • 2,907
  • 1
  • 19
  • 30
0

All reference types are nullable. String is a reference type.

Tom Squires
  • 8,848
  • 12
  • 46
  • 72
  • exactly. so I wonder how to make the compiler aware explicitely that this value is potentially nullable by construction VS potentially null because of a bug – nicolas Feb 19 '13 at 16:36
  • @nicolas If the type that you're assigning to is a reference type, then you can assign null. If it's a struct (other than `Nullable`, which has special compiler support) then you can't. If you have a generic method and so don't know the type, you can either use `default(T)` which will be `null` for all nullable types, or `where T : class` to ensure that only reference types are valid generic arguments. – Servy Feb 19 '13 at 16:45
  • @Servy if being able to assign to null is what is at stake for you guys, I understand why you don't get the question.. The point is to *isolate* null, and track where it can go, not enjoy freedom of null assigning everything... – nicolas Feb 19 '13 at 16:57
  • @nicolas So you're proposing that C# add a language feature to state that a type is nullable? If so, this is not the appropriate place for such a request, this is not where Microsoft goes to look at feature requests for C#, and so the question is off topic. – Servy Feb 19 '13 at 16:59
  • 3
    @nicolas: I agree, it would be *very, very* good, if you could declare reference types as *not* nullable. Currently, all reference types are nullable, so no special declaration exists to tell the compiler they are nullable, because they all are. – Daniel Hilgarth Feb 19 '13 at 17:01
  • @nicolas Are you asking for a language feature, or not? So far nobody has any idea what you're actually asking. – Servy Feb 19 '13 at 17:01
  • @Servy that is not a request, that is a hint at how you probably should look at nullable. – nicolas Feb 19 '13 at 17:02
  • 1
    @nicolas You should really stop spending your time telling everyone that they don't know what they're talking about and spend more time trying to explain what it is that you're asking. Insulting the people that you're asking for help about is not particularly productive. – Servy Feb 19 '13 at 17:04
  • @DanielHilgarth exactly, but I could somehow fake it, by following the convention, for instance that I only instanciate Mynullable and make sure that every assignment of value is made with non-null references. of course it does not guarantee me anything at the boundaries, but I could add some checks there. by following this convention, I'd have a compiler check (Mynullable is not assignable from T) etc.. I could still have a null Mynullable but at least I'll get rid of some class of bugs with this semantic checked at compile time – nicolas Feb 19 '13 at 17:09
  • @nicolas: Please see [my answer](http://stackoverflow.com/a/14963599/572644). Unfortunatelly, that's not working either. – Daniel Hilgarth Feb 19 '13 at 17:23