Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullable references types and ToString() overload

Please, note that this question is about the latest C# 8 nullable-references, I've enabled it in csproj file by the following <Nullable>enable</Nullable> declaration.

Consider the following simple code

class SortedList<T> where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
{
    private Node? _node;
    private readonly IComparer<T> _comparer = Comparer<T>.Default;

    class Node
    {
        public Node(T value)
        {
            Value = value;
        }

        public T Value { get; }
        public Node? Next { get; set; }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    //rest of code, that isn't important
}

The line return Value.ToString(); gives me a CS8603 Possible null reference return warning and my question actually why is it here?

I'm using the where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable generic constraint to match a numeric types, Value is value type actually, not the reference one. There is also no overload of ToString() to any value type, the default implementation for Int32 (for example) returns non-nullable string. MSDN notes to inheritors is also saying that

Your ToString() override should not return Empty or a null string.

Does the compiler complaining about some type, which can satisfy the generic constraint and return null from ToString()?

I can avoid the warning by making a return type nullable

public override string? ToString()
{
    return Value.ToString();
}

or by using the null-coalescing operator

public override string ToString()
{
    return Value.ToString() ?? "";
}

or by null-forgiving operator

public override string ToString()
{
    return Value.ToString()!;
}

But these options look like a tricks mostly, I'm looking for explanation of this behavior, why is there, by design or any other reasons were take place? Is there any ways to avoid the warning, except those above?

Btw, this option doesn't work, warning still in place

[return: MaybeNull]
public override string ToString()
{
    return Value.ToString();
}

I'm using .NET Core 3.1 and VS 2019 16.4.2, but I don't think it's really important here. Thanks in advance for help!

like image 572
Pavel Anikhouski Avatar asked Dec 07 '22 11:12

Pavel Anikhouski


1 Answers

The signature for object.ToString() is:

public virtual string? ToString()

That is, object's ToString() method is defined as returning a string which may be null.

Your overload of Node.ToString() tightens this requirement and promises to return a non-null string. This is fine. Int32 does this, for example (as you noted).

However, your Node.ToString() method returns the value from Value.ToString(). We just saw that this ToString method (i.e. object.ToString()) might return null. Therefore the compiler's warning you that your Node.ToString() method might inadvertently return null, if Value.ToString() returns null.


This explains why you found that declaring Node.ToString() as:

public override string? ToString()

suppressed the warning: you're now declaring that your Node.ToString() method might return null, so it's not a problem if Value.ToString() returns null and you then return this value.

It also explains why writing return Value.ToString() ?? ""; suppressed the warning: if Value.ToString() returned null, that code would ensure that Node.ToString() did not return null.


How best to fix this? You decide.

Do you want to promise that your Node.ToString() method never returns null? If so, you'll need to figure out what to do if Value.ToString() returns null.

Otherwise, it's probably best to follow the established pattern, and say that your Node.ToString() method might return null.


Why does object.ToString() return string?? See this thread for a full discussion, but the gist is that there are ToString methods in the wild which do return null, because some people don't follow the guideline that you should never return null or an empty string.

  1. If you are referencing a type which was built without nullable annotations, the fact that object.ToString() returns string? means that you'll be given a warning unless you check for null. This protects you from a badly-written ToString methods.
  2. If you a referencing a type which was built with nullable annotations, then either:
    1. The author followed the guidelines, and declared their ToString method as returning string. In this case, the compiler assumes that you won't get null.
    2. The author explicitly did not follow the guidelines, and declared their ToString method as returning string?. In this case, you're forced to check for null.

Note that when you create an overload of ToString in Visual Studio, the generated method returns string (even if the method being overloaded returns string?). This prompts you to follow the guidelines.

The only annoyance here is when you're dealing with a generic type, or a type which has been cast to object. In this case, the compiler doesn't know whether the object's ToString method is following the guidelines or not. Because object.ToString returns string?, the compiler assumes the worst. You can override this assumption if you wish with the null-forgiving operator !.

like image 72
canton7 Avatar answered Dec 30 '22 10:12

canton7