10

Consider an example:

class Test {

    string S { get; set; }

    public Test() {
        Init();
    }

    private void Init() {
        S = "hello";
    }
 
}

Using nullable C# project feature, this sample will trigger a compiler warning:

Warning CS8618 Non-nullable property 'S' must contain a non-null value when exiting the constructor. Consider declaring the property as nullable.

However, the property DOES contain non-null value when exiting the constructor, it's just not set directly in the constructor, but indirectly in a method that is unconditionally called from the constructor.

This example shows clearly that there is no possibility for the S property to ever be null. When an instance of the Test class is created, the Init() method is called unconditionally, so the S property is ALWAYS set to "hello".

Of course, this warning can be suppressed in code, but this looks just fugly. Is it a better way to tell the compiler I really did set the S property to a non-null value elsewhere?

BTW, if you really wonder WHY to set values in the constructor indirectly, let's consider there is another derived property D of Derived type. To create an instance of Derived the string must be parsed first and we don't want to parse the string each time we read the D property.

So, the more realistic code would look more like this:

class Test {

    public string S { 
        get => _S;
        set => D = new Derived(_S = value);
    }

    public Derived D { get; private set; }

    public Test(string s) => D = new Derived(_S = s);

    private string _S;
 
}

As you can see, both S and D are set to non-null values when exiting the constructor. Yet the code still triggers the compiler warning CS8618.

Squti
  • 4,171
  • 3
  • 10
  • 21
Harry
  • 4,524
  • 4
  • 42
  • 81
  • Good question & the answer is the the right solution to your first example. However, your 'more realistic' code wouldn't generate any errors - `_S` *is* set in the constructor. `S` itself doesn't need to be set as its getter returns something non-nullable. – Charles Mager Nov 22 '21 at 14:53
  • As for your other question, I do not add to my answer since I am not sure if I am right, someone please correct if I am wrong. Compiler isn't that smart to know your intention of using another method (that's why you have to mark that method) to set S value. Technically it could go deeper to the calls but it's just not worth it IMO. Moreover, what if your `Init` method is `virtual` and a derived type simply does not initialize `S`? Too many scenarios IMO and decorating with attributes to tell compiler "I know what I am doing" is better. – Luke Vo Nov 22 '21 at 14:57
  • @LukeVo: When the method is marked as virtual - that would be a solid reason to trigger the compiler warning then. When the method is not virtual, it cannot be overridden, so the method initializing the S must be called. Otherwise trying to override `Init` would trigger the compiler error instead. In case of method hiding - the original Init would be called anyway. But you made me curious - is it possible to break the code somehow with using `MemberNotNullAttribute`? I guess the attribute exists for the CA performance reason, just as you've mentioned. – Harry Nov 22 '21 at 15:37
  • @Harry I tried changing it to `protected virtual void Init()` and override it with a derived type. If you do not call `base.Init()`, you receive the same warning. About compiler non-virtual `Init`, performance would be one reason IMO, the other is, `Init` may call further more methods `InitS` calling `InternalInitS` down the line and it's just not worth it. – Luke Vo Nov 22 '21 at 15:45

1 Answers1

17

Use MemberNotNullAttribute to mark your function:

using System.Diagnostics.CodeAnalysis;

class Test
{

    string S { get; set; }

    public Test()
    {
        Init();
    }

    [MemberNotNull(nameof(S))]
    private void Init()
    {
        S = "hello";
    }

}

The compiler will now complain if you do not initialize S in Init:

enter image description here

See more scenarios in this article: Attributes for null-state static analysis

Luke Vo
  • 17,859
  • 21
  • 105
  • 181
  • This is exactly what I was looking for. They thought about everything. I guess if it would be automatic, it could make CA considerably slower and maybe even vulnerable for mistakes. Maybe it's even: "If you make such indirection, please note it clearly and explicitly in the code" reason, that is also pretty justified. – Harry Nov 22 '21 at 15:39