【问题标题】:Nullability of argument doesn't match constraint type参数的可空性与约束类型不匹配
【发布时间】:2020-03-13 10:19:00
【问题描述】:

我写了如下扩展方法:

public static void NotifyChanged<T>(this INotifyPropertyChanged inpc, ref T current, T newValue, Action<PropertyChangedEventArgs> eventRaiser, [CallerMemberName] string? name = null) where T : IEquatable<T> {
    if (current.Equals(newValue)) { return; }
    current = newValue;
    eventRaiser(new PropertyChangedEventArgs(name));
}

可以这样使用:

public class Foo : Bar, INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    private string? rootExpression;

    public string? RootExpression {
        get => rootExpression;
        set => this.NotifyChanged(ref rootExpression, value, args => PropertyChanged?.Invoke(this, args));
    }
}

这节省了编写 INPC 感知属性的大部分样板文件。

但是,我现在在调用 NotifyChanged 时收到编译器警告错误:

类型“字符串?”不能在泛型类型或方法“INotifyPropertyChangedExtensions.NotifyChanged(INotifyPropertyChanged, ref T, T, Action, string?)”中用作类型参数“T”。类型参数“字符串”的可空性?与约束类型“System.IEquatable”不匹配。

AFAICT 错误是说 string? 不能转换为 IEquatable&lt;string?&gt;,只有 string 可以转换为 IEquatable&lt;string&gt;

我该如何解决这个问题?应用一些属性?还是别的什么?

【问题讨论】:

    标签: c# nullable-reference-types


    【解决方案1】:

    你的问题是:

    where T : IEquatable<T>
    

    这表示T 必须是不可为空的IEquatable&lt;T&gt;。您希望它可以为空。您可以通过添加? 来表示:

    where T : IEquatable<T>?
    

    请注意,这将在if (current.Equals(newValue)) 上抱怨,如果currentnull,则会抛出。


    执行此操作的正常方法不是将T 限制为IEquatable&lt;T&gt;,而是使用EqualityComparer&lt;T&gt;.Default。如果T 实现IEquatable&lt;T&gt;,这将为您提供一个调用IEquatable&lt;T&gt;.Equals 的相等比较器,否则它会回退到调用普通object.Equals

    如果 currentnull,这也解决了您的 NRE 问题:

    public static void NotifyChanged<T>(
        this INotifyPropertyChanged inpc,
        ref T current, T newValue,
        Action<PropertyChangedEventArgs> eventRaiser,
        [CallerMemberName] string? name = null)
    {
        if (EqualityComparer<T>.Default.Equals(current, newValue)) { return; }
        current = newValue;
        eventRaiser(new PropertyChangedEventArgs(name));
    }
    

    传递eventRaiser 也很少见:通常你会在实现INotifyPropertyChanged 的基类上创建NotifyChanged 方法,而不是使其成为扩展方法。然后你可以让NotifyChanged 引发PropertyChanged 事件本身,或者你编写另一个方法,例如OnPropertyChanged 引发PropertyChanged 并从NotifyChanged 调用它。

    如果你确实想通过事件来加注,你可以通过PropertyChangedEventHandler

    public static void NotifyChanged<T>(
        this INotifyPropertyChanged _,
        ref T current, T newValue,
        PropertyChangedEventHandler eventHandler,
        [CallerMemberName] string? name = null)
    {
        if (EqualityComparer<T>.Default.Equals(current, newValue)) { return; }
        current = newValue;
        eventHandler?.Invoke(this, new PropertyChangedEventArgs(name));
    }
    
    this.NotifyChanged(ref rootExpression, value, PropertyChanged);
    

    【讨论】:

    • 我不能让NotifyChanged 引发事件,因为它是一种扩展方法;类的事件只能在该类中引发。
    • 我试图避免检查相等性并为每个属性引发事件的样板。理想情况下,我会按照您的建议从具有 NotifyChanged 方法的 INPC 实现基类继承;但在这种情况下我不能这样做:我必须从一个我无法修改的特定类继承。
    • @ZevSpitz 在这种情况下,最后的特定位不适用于您。然而,前两部分确实如此
    • 但我确实喜欢您关于传入处理程序而不是委托的建议。
    • 也适用于:where T : class?
    猜你喜欢
    • 2016-11-07
    • 2020-02-06
    • 1970-01-01
    • 1970-01-01
    • 2020-03-09
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多