简体   繁体   中英

nullable string in C#

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.

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.

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#

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM