【问题标题】:Is it bad practice to have Events in interfaces? c#在接口中有事件是不好的做法吗? C#
【发布时间】:2018-09-05 01:45:34
【问题描述】:

我正在尝试更多地使用接口,并且经常发现自己将事件放入接口中。感觉很奇怪,因为我要让实现者知道何时在他们的实现中引发这些事件。

例如,我有一个 ILevellable 接口,它简单地说明一个对象应该有 xp、一个级别和一个最大级别,以及一些修改这些值的方法。引用 ILevellable 的类可能想知道它什么时候升级,所以我为 OnLevelUp 添加了一个动作......但实现可能没有把它放在正确的位置。

public interface ILevellable
{
    int Level { get; }
    int MaxLevel { get; }
    int XP { get; }
    event Action<bool> OnXPChanged;
    event Action<ILevellable> OnLevelUp;
    event Action OnMaxLevelReached;

    void LevelUp(int newLevel);
}

我应该相信实现这个接口的用户会知道在哪里实现它吗?我会假设不会。尤其是因为某些事件可能有参数,同样,用户可能不知道它应该代表什么。

感谢任何指导!我是这样工作的新手。

谢谢!

【问题讨论】:

  • 这可能会被标记为“基于意见”,这可能就是您需要的全部答案。我认为如果事件有 very good 评论描述,例如,Action&lt;bool&gt; 中的 bool 是什么,这没关系。或者,对于发布到野外的接口,我可能会使用EventHandlerEventArgs,它们强制对事件处理程序进行非常正式的定义。我认为它们对于内部代码来说过于冗长,但是....
  • 请注意,在接口中实现属性和方法几乎同样容易,而对于试图实现该接口的人来说,这些属性和方法是完全难以理解的。 (比如我的LevelUp的实现如果传入低于当前级别的级别该怎么办?)
  • @zzxyz 是的,我在想拥有 EventArgs 或只是一些数据对象也可能是一个很好的解决方案......感谢您的“意见”:D 我现在就评论我的东西......
  • @SebastianKing - 请记住,接口仅描述接口 - 它们描述行为。这不仅适用于事件,还适用于您公开的所有属性和方法。通常,只有当您将接口和一组单元测试配对时,您才会指定行为。
  • @zzxyz - 接口可能暗示了行为,但无法明确指定行为。您要么必须描述行为,要么提供单元测试——但这些都不是 C# 强制执行的。

标签: c# interface delegates


【解决方案1】:

在您的界面中定义事件很好。但是,将“发送者”和“事件参数”都传递到事件中是符合惯例的。 TypedEventHandler 是一种简单的方法。例如:

using Windows.Foundation;

public struct LevellableChange {
    public LevellableChange( int dl, int dxp) 
    { 
         this.ChangeInLevel = dl;
         this.ChangeInXP = dxp;
    }
    int ChangeInLevel { get; }
    int ChangeInXP {get;}
}

public interface ILevellable
{
    int Level { get; }
    int MaxLevel { get; }
    int XP { get; }
    event TypedEventHandler< ILevellable, LevellableChange> Changed;
}

那么实现它们的标准方法是这样的:

public class Levellable: ILevellable
{
    public event TypedEventHandler<ILevellable, LevellableChange> Changed;

    public int Level {
        get {
            return this._level;
        }
        private set {
            if (value != this._level) {
                int oldLevel = this._level;
                this._level = value;
                this.Changed?.Invoke(this, new LevellableChange(value - oldLevel, 0));
            }
        }
    }
    private int _level;

    // similar for XP. 
}

另外一个评论,有一个实现 INotifyPropertyChanged 的​​类很有用,然后养成从它派生的习惯。与 Visual Studio 中的 sn-p 一起,它使事情变得容易得多。确实,对于上面的示例,INotifyPropertyChanged 是不够的,因为它不会对先前的值进行更改。

【讨论】:

  • 注意,事件可能是内存泄漏的原因。如果对象 A 正在侦听由对象 B 实现的事件,那么即使不再需要 A,只要 B 被引用/被使用,它也会被保存在内存中。为避免这种情况,您需要: 从事件中取消订阅 A;或者确保 B 的生命周期不比 A 的期望生命周期长很多;或使用弱事件处理程序。
【解决方案2】:

首先,您的接口定义中没有委托类型,而是事件。检查 Jon Skeet 的 this answer 以查看代表和事件之间的区别。在说在您的界面中包含事件完全没问题之后,即使是 .Net 也可以,例如考虑著名的INotifyPropertyChanged 界面。它只是定义了一个事件...

其次:

我应该相信实现这个接口的用户会知道在哪里实现它吗?

当您定义一个接口时,您实际上并不像关心消费者那样关心实现者,因此,对于看到您的合同定义的消费者来说,知道他/她可以订阅名为 OnLevelUp 的事件很有用.假设我确实在使用您的界面,我不知道您如何或何时触发 OnLevelUp 事件,我应该不在乎,因为您为该事件赋予了一个非常语义和有意义的名称,所以我几乎可以确保当ILevellable的Level属性增加时会触发。

【讨论】:

  • 哦,我的错。好的,我明白你在说什么。我想我希望会有某种“安全”的解决方法,但我想它确实只是归结为命名。也感谢您的链接。我会读的:)。
猜你喜欢
  • 2011-10-23
  • 1970-01-01
  • 2012-08-17
  • 2011-08-22
  • 1970-01-01
  • 2015-05-31
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
相关资源
最近更新 更多