Home > OS >  Generic NotNullWhen for Func argument
Generic NotNullWhen for Func argument

Time:10-18

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());

See here on SharpLab.


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
    }
  • Related