I have a function like this:
public class X {
public T GetOrNew<T, TKey>(TKey? key, Func<TKey?, T> factory){
// omitted code
var instance = factory(key);
// omitted code
return instance
}
}
Now the problem is that the following usage generates nullable warnings:
// Here key is never allowed to be null.
public Person GetPerson(StreetIndex key){
_ = key ?? throw new ArgumentNullException(paramName: nameof(key));
var x = new X();
// so here key is never null, so index is never null
x.GetOrNew(key, index => index.GetRandomPerson()); // warning on index.GetRandomPerson because it could be null according to the signature.
// this call should also work and not give any nullable warnings.
x.GetOrNew<StreetIndex, Person>(null, _ => new PersonFactory.Birth());
}
Of course looking at the code it becomes clear that the key
passed to the factory is only null
when the parameter to GetOrNew
is null
.
Is there any attribute (or pattern) to indicate this?
For "normal" functions such as Person? Mogrify(StreetIndex? key)
I can use [return: NotNullIfNotNull(nameof(key))
.
Note that I do need to support null
as valid parameter to GetOrNew
.
CodePudding user response:
Your current signature is saying "Even when TKey
is a non-nullable type, key
and that factory
parameter can be null".
Instead, you can declare your method:
public T GetOrNew<T, TKey>(TKey key, Func<TKey, T> factory)
This says "When TKey
is a nullable type, then key
and that factory
parameter can be null; otherwise if TKey
is a non-nullable type, then key
and that factory
parameter cannot be null".
This means that this is permitted:
AnotherClass? key = null;
GetOrNew(key, theKey => new AnotherClass());
And this doesn't generate nullable warnings:
AnotherClass key = new AnotherClass();
GetOrNew(key, theKey => theKey.DoIt());
Note that:
GetOrNull(null, _ => ...);
Can never work, regardless of nullability. The compiler needs to infer a type for TKey
, and the only information you've given is that it can be null
, which isn't enough information for it to go on.
You'll either need to specify TKey
explicitly:
GetOrNull<StreetIndex?, Person>(null, _ => ...);
Or use a typed null:
GetOrNull((StreetIndex?)null, _ => ...)
GetOrNull(default(StreetIndex?), _ => ...)
Or indeed, give the type information in the factory
parameter:
GetOrNull(null, (StreetIndex? _) => ...)
CodePudding user response:
So what happens if theKey
is null? It will throw an exception. What would you like to happen? e.g. you could return a new object
x.GetOrNew(key, theKey => theKey?.DoIt() ?? new();
edit: it seems you want to give your user flexibility on how to call GetOrNew
. You could do the null checking in the implementation of that method, e.g.
var instance = key is not null ? factory(key) : new();
Note that GetOrNew(null, _ => new Whatever())
will likely not work, as the type of TKey
cannot be determined from the first argument. (At best it will be object?
).
You could add an overload to support that:
public class X
{
#nullable enable
public T GetOrNew<T, TKey>(TKey? key, Func<TKey?, T> factory)
where T : class, new()
{
// omitted code
var instance = key is not null ? factory(key) : new();
// omitted code
return instance;
}
public T GetOrNew<T>(object? key, Func<object?, T> factory)
where T : class, new() =>
GetOrNew<T, object?>(key, factory);
#nullable restore
}