实现 INotifyPropertyChanged 的差异

本文关键字:INotifyPropertyChanged 实现 | 更新日期: 2024-10-31 16:12:45

我正在学习WPF,并且仍在尝试完全理解INotifyPropertyChanged接口。我找到了以下示例:

public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)
    }   
}

该示例遵循我经常在其他代码中看到的相同格式。在Visual Studio 2013中,我可以选择允许IDE为我显式实现接口。这样做会创建以下内容:

   event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
   {
       add { throw new NotImplementedException(); }
       remove { throw new NotImplementedException(); }
   }

这两者有何不同?如果我决定使用 Visual Studio 生成的代码,与第一个示例相比会如何?

实现 INotifyPropertyChanged 的差异

这里确实有两个问题。 首先,事件中的添加和删除是怎么回事,其次,什么是显式接口实现? 这些是正交的东西。

具有添加和删除功能的事件

在 C# 中,事件实际上很像属性。 事件具有add方法和remove方法。 如果未显式指定它们,编译器将为您创建它们以及一个委托字段来支持事件(您可以通过引用事件从您自己的类中访问该字段)。

如果您愿意,可以自己创建addremove方法(如果要显式实现接口,则必须这样做)。 在这种情况下,您通常还会创建一个委托字段来支持您的事件:

public event EventHandler SomeEvent
{
    add { mSomeEvent += value; }
    remove { mSomeEvent -= value; }
}
private void RaiseSomeEvent()
{
    var handler = mSomeEvent;
    if( handler != null ) handler( this, EventArgs.Empty );
}
private EventHandler mSomeEvent;

请注意,如果要引发事件,则必须引用支持字段。 您不能再使用事件名称本身来执行此操作。 您实际上可以为INotifyPropertyChange执行此操作,而无需诉诸显式实现。

显式接口实现

显式实现接口时,实际上创建了接口成员的"私有"版本。 现在,我将 private 放在引号中,因为实现实际上不是私有的。 仅当从接口类型访问强制转换时,才能访问实现。 这有点拗口,所以这里有一个例子:

public interface IFoo
{
    int Bar { get; }
}
public class A : IFoo
{
    int IFoo.Bar
    {
        get { return -1; }
    }
}

现在,假设我们在某个地方的方法中有以下内容:

var a = new A();
int bar = a.Bar;

这将生成编译错误,因为类型 A 没有名为 Bar 的公开可见成员。 但是,如果我们先投IFoo

var a = new A();
int bar = ((IFoo) a).Bar;

现在它编译,当它运行时,bar == -1. 您也可以将变量a强类型化为IFoo

IFoo a = new A();
int bar = a.Bar;

那也行得通。 因此,可以从类外部(甚至程序集外部)访问此成员,但只能直接通过声明接口类型访问。 这对于隐藏您不想支持的实现(例如IList<T>的可变部分)或根据接口有不同的实现(例如IEnumerable中的GetEnumerator()而不是IEnumerable<T>中的GetEnumerator())非常有用。

public class B : IFoo
{
    public int Bar { get { return 2; } }
    int IFoo.Bar { get { return 1; } }
}

现在,如果我们像这样使用这个类:

B b = new B();
IFoo bAsIFoo = b;
int barFromB = b.Bar;
int barFromFoo = bAsIFoo.Bar;

你会在这里得到的是barFromB == 2barFromFoo == 1

我建议阅读.NET中的事件和事件处理程序。

我们称之为 A

public event PropertyChangedEventHandler PropertyChanged;

和这个 B

event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
    add { throw new NotImplementedException(); }
    remove { throw new NotImplementedException(); }
}

相似,但也有不同。

  1. PropertyChanged是显式的(您已经为其指定了访问修饰符),而 B PropertyChanged是隐式公共的,因为您正在为公共接口提供实现。
  2. A PropertyChanged 实现了默认的add/remove,它只是 PropertyChanged += handlerProeprtyChanged -= handler 的包装器,其中 B PropertyChanged实现add/remove作为throw new exception。因此,如果有任何内容试图订阅 B 'PropertyChanged' 事件,它将抛出NotImplementedException
  3. 任何使用您的班级或您的班级作为INotifyPropertyChanged的班级的人都可以访问PropertyChanged事件,而作为 B PropertyChanged事件只有在有人将您的类投射到INotifyPropertyChanged时才能访问。

现在你的另一段代码:

public void NotifyPropertyChanged(string propertyName)
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)
    }   
}

用于调用 PropertyChanged 事件(如果不是 null),并通知任何订阅者给予者propertyName已更改。每当你想要从外部代码调用类上的事件时,你都需要,因为事件只能从你的类中调用。