The message warning CS8604: Possible null reference argument
is issued on the call to Process()
. A way to fix is to mark it with MemberNotNull
.
If there is no override in the Derived class then MemberNotNull works as expected.
Why does the compiler still warn if an override exists?
What would be a better way to suppress this warning?
using System;
using System.Diagnostics.CodeAnalysis;
namespace Tests
{
class Base
{
public virtual string? Member { get; set; }
[MemberNotNull(nameof(Member))]
public void Validate()
{
if (Member is null)
{
throw new Exception("Prop is null");
}
}
}
class Derived : Base
{
public override string? Member { get; set; } // warning CS8604: Possible null reference argument
}
[TestClass]
public class MemberNotNullTests
{
static void Process(string s)
{
Console.WriteLine(s);
}
[TestMethod]
public void TestMemberNotNull()
{
Derived instance = new();
instance.Validate();
Process(instance.Member);
}
}
}```
CodePudding user response:
(This answer is non-authoritative)
From what I can tell, this behaviour is likely by-design for now, apparently.
Here's some relevant sources:
Here's the LDM that lead to the creation of
[MemberNotNull]
in the first place.Here's the PR from February 2020 that added support for
[MemberNotNull]
to Roslyn, the C# compiler.Here's a (currently open) GitHub issue about the reverse scenario: where a derived-type cannot use
[MemberNotNull]
to make assertions about members inherited from a supertype.Here's an earlier dupe of that issue that was actually closed as by design, except with a Roslyn team-member stating for the record:
@jaredpar 2021-07-07: The C# language design team decided that
MemberNotNull
could only apply to declared members on the type where the attribute was used.So even though he's referring to use of
[MemberNotNull]
on a derived-type referring to supertype members, it also precludes[MemberNotNull]
being used in a supertype to refer to possibly overridden subclass members.Remember that overridden members don't share identity with their corresponding
abstract
orvirtual
members in their supertype (whereas inherited nonvirtual members do share identity), so (as far as the analyzer is concerned) [MemberNotNull( "Member" )]can only refer to
Base.Memberand not
Derived.Member`.
It can certainly be argued that it should be supported, I suspect it's a consequence of the decision to use the same member lookup code as the long-existing
DefaultMemberAttribute
.
This is indirectly mentioned in the LDM notes, but they don't explain why or how they came to that decision, but I assume because it would save time and result in predictable behaviour (or in this case... unexpected behaviour):
We propose that the lookup rules for resolving the names in these instances would be similar to the rules for the
DefaultMemberAttribute
.
Curiously, in the PR code-review notes the reverse scenario is discussed where MemberNotNull
is used in a subclass to assert that non-overridden inherited members are not-null - but I can't find a mention of the obvious reversal of that, which is what we're concerned:
@jcouv 2022-02-25: Note that a type can only mark its own members as not-null, but not its base's.
CodePudding user response:
Why you are using string?
Why do you write validation inside the class that is going to be validated?
Are you sure that the derived class should override property?
I could recommend having:
class Base
{
public virtual string Property {get;set;}
}
class Derived: Base
{
public override string Property {get;set;}
}
// can be as service
public interface IValidator<T> where T: Base
{
public bool IsValid(T item);
}
class Validator : IValidator<T>
{
public bool IsValid(T obj) => string.IsNullOrEmpty(obj.Property);
}
In tests:
Derived instance = new();
Validator validator = new();
validator.Validate(instance);
Process(instance.Member);