Func 参数的通用 NotNullWhen

Generic NotNullWhen for Func argument

我有这样的功能:

public class X {
public T GetOrNew<T, TKey>(TKey? key, Func<TKey?, T> factory){

// omitted code

var instance = factory(key);

// omitted code

return instance

}

}

现在的问题是以下用法会生成可为空的警告:


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

}

当然,看代码就会清楚,当 GetOrNew 的参数是 null 时,传递给工厂的 key 只是 null

是否有任何属性(或模式)表明这一点?

对于 Person? Mogrify(StreetIndex? key) 等“正常”功能,我可以使用 [return: NotNullIfNotNull(nameof(key))

请注意,我确实需要支持 null 作为 GetOrNew 的有效参数。

您当前的签名是说“即使 TKey 是不可为 null 的类型,key 并且 factory 参数也可以为 null”。

相反,您可以声明您的方法:

public T GetOrNew<T, TKey>(TKey key, Func<TKey, T> factory)

这表示“当 TKey 是可空类型时,则 key 并且 factory 参数可以为空;否则,如果 TKey 是不可空类型, 然后 key 并且 factory 参数不能为 null".

这意味着这是允许的:

AnotherClass? key = null;
GetOrNew(key, theKey => new AnotherClass());

并且这不会生成可为 null 的警告:

AnotherClass key = new AnotherClass();
GetOrNew(key, theKey => theKey.DoIt());

See here on SharpLab.


注意:

GetOrNull(null, _ => ...);

永远无法工作,无论是否为空。编译器需要推断 TKey 的类型,而您提供的唯一信息是它可以是 null,这不足以让它继续。

您需要明确指定 TKey

GetOrNull<StreetIndex?, Person>(null, _ => ...);

或使用类型化的 null:

GetOrNull((StreetIndex?)null, _ => ...)
GetOrNull(default(StreetIndex?), _ => ...)

或者实际上,在factory参数中给出类型信息:

GetOrNull(null, (StreetIndex? _) => ...)

那么如果 theKey 为空会发生什么?它会抛出异常。你希望发生什么?例如你可以 return 一个新对象

x.GetOrNew(key, theKey => theKey?.DoIt() ?? new();

编辑:您似乎想让用户灵活地调用 GetOrNew。您可以在该方法的实现中进行空检查,例如

var instance = key is not null ? factory(key) : new();

请注意,GetOrNew(null, _ => new Whatever()) 可能不起作用,因为无法从第一个参数确定 TKey 的类型。 (最好是 object?)。

您可以添加一个重载来支持:

    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
    }