【问题标题】:C# Language Design: explicit interface implementation of an eventC#语言设计:事件的显式接口实现
【发布时间】:2010-02-15 18:27:06
【问题描述】:

关于C#语言设计的小问题:))

如果我有这样的界面:

interface IFoo {
  int Value { get; set; }
}

可以使用 C# 3.0 自动实现的属性显式实现此类接口:

sealed class Foo : IFoo {
  int IFoo.Value { get; set; }
}

但如果我在界面中有事件:

interface IFoo {
  event EventHandler Event;
}

并尝试使用类似字段的事件显式实现它:

sealed class Foo : IFoo {
  event EventHandler IFoo.Event;
}

我会得到以下编译器错误:

error CS0071: An explicit interface implementation of an event must use event accessor syntax

我认为类场事件是自动实现属性的某种二元性。

所以我的问题是:这种限制的设计原因是什么?

【问题讨论】:

  • 我确信 Andreas Huber 的解释是正确的。您可以通过从显式实现调用另一个私有事件来解决此问题。这样,您将从类似字段的事件功能中获得所有自动功能(线程安全等)。示例:sealed class Foo : IFoo { event EventHandler privateEventFieldLike; event EventHandler IFoo.Event { add { privateEventFieldLike += value; } remove { privateEventFieldLike -= value; } } }

标签: c# events interface explicit-implementation


【解决方案1】:

我想这可能与您无法从该类的其他成员调用显式接口实现的事实有关:

public interface I
{
    void DoIt();
}

public class C : I
{
    public C()
    {
        DoIt(); // error CS0103: The name 'DoIt' does not exist in the current context
    }

    void I.DoIt() { }
}

请注意,您可以通过先向上转换到接口来调用该方法:((I)this).DoIt();。有点难看,但它有效。

如果事件可以按照 ControlFlow(OP)的建议显式实现,那么您将如何实际提出它们?考虑:

public interface I
{
    event EventHandler SomethingHappened;
}

public class C : I
{
    public void OnSomethingHappened()
    {
        // Same problem as above
        SomethingHappened(this, EventArgs.Empty);
    }

    event EventHandler I.SomethingHappened;
}

在这里你甚至不能通过首先向上转换到接口来引发事件,因为事件只能从实现类中引发。因此,为显式实现的事件要求访问器语法似乎非常有意义。

【讨论】:

  • 好答案。对我来说似乎是合理的。谢谢。
  • 我同意。类字段事件由三部分组成:add 访问器、remove 访问器以及对委托类型的底层生成字段的私有访问。当您使用 MyEvent != nulllocal = MyEventMyEvent(sender, args) 等时,直接使用该委托字段。但是,只有 addremove 访问器是接口“合同”的一部分。
  • 在 C# 6.0 (2015) 中,可以创建一个 get-only 的自动属性。然后可以从同一类的构造函数分配给它。这确实分配给了基础领域。例如sealed class Foo { public int Value { get; } public Foo() { Value = 42; } }。如果您尝试使用显式接口实现来执行此操作,C# 编译器允许它,但您不能再在构造函数中赋值(支持字段不是 get-only 接口“合同的一部分” ”)。 sealed class Foo : IFoo { int IFoo.Value { get; } public Foo() { } }
【解决方案2】:

有趣的问题。我浏览了语言注释档案,发现这个决定是在 1999 年 10 月 13 日做出的,但注释并没有说明这个决定的理由。

在我的脑海中,我看不出有任何理论或实际原因说明我们不能有类似字段的显式实现事件。我也看不出我们特别需要这样做的任何理由。这可能仍然是未知的谜团之一。

【讨论】:

  • 现在这就是为什么我有时仍会访问 SO... 人们还会在哪里接受“我不知道”作为一个好的答案。
  • 似乎原因可能是您永远无法引发这样的事件,除非显式实现的事件具有与显式实现的方法不同的可见性规则,如我的回答中所述...
  • 我认为 Andreas Huber 的回答是合理的。
【解决方案3】:

当显式实现在接口中声明的事件时,您必须手动提供通常由编译器提供的添加和删除事件访问器。访问器代码可以将接口事件连接到您的类中的另一个事件或它自己的委托类型。

例如,这将触发错误 CS0071:

public delegate void MyEvent(object sender);

interface ITest
{
    event MyEvent Clicked;
}

class Test : Itest
{
    event MyEvent ITest.Clicked;  // CS0071
    public static void Main() { }
}

正确的方法是:

public delegate void MyEvent(object sender);

interface ITest
{
    event MyEvent Clicked;
}

class Test : Itest
{
    private MyEvent clicked;

    event MyEvent Itest.Clicked
    {
        add
        {
            clicked += value;
        }

        remove
        {
            clicked -= value;
        }
    }

    public static void Main() { }
}

Compiler Error CS0071

【讨论】:

  • +1 表示正确的实现,但这不是问题所在。人们倾向于在问题的最底部找到真正的肉。有点像你想在告诉父母你被拘留之前解释你为什么做了你所做的事情。
  • +1 也许不是这个问题的明确答案,但这可能为我节省了一个很好的时间来尝试解决问题。
【解决方案4】:

这实际上不是我自己的原创想法。

不过,我想我可能会对此作出回应:

“在我的脑海中,我没有看到任何理论上或实际的原因说明我们不能有类似字段的明确实现的事件。我也没有看到我们特别需要的任何原因。这可能必须保留一个未知的奥秘。” -埃里克·利珀特


程序员对 C# 的介绍第二版的第 23 章中,Eric Gunnerson 写道:

"[I]如果另一个类也想在单击按钮时被调用,则可以使用 += 运算符,如下所示:

button.Click += new Button.ClickHandler(OtherMethodToCall);

不幸的是,如果其他班级不小心,它可能会执行以下操作:

button.Click = new Button.ClickHandler(OtherMethodToCall);

这很糟糕,因为这意味着我们的 ButtonHandler 将被取消挂钩,并且只会调用新方法。”

...

“需要某种保护委托字段的方法,以便仅使用 += 和 -= 访问它。”


他在接下来的几页中继续评论包括 add() 和 remove() 方法来实现此行为;能够直接写入这些方法以及为不需要的委托引用分配存储空间的结果。

我会添加更多,但我太尊重作者了,未经他的许可就这样做了。我建议找到这本书的副本,并且一般会推荐 Eric Gunnerson 的任何内容(博客等...)

无论如何,我希望这与主题相关,如果是的话,希望它能照亮这个“未知之谜”吗? (我正在阅读这一章并搜索 Stack Overflow 以深入了解从自定义对象创建自定义集合时的事件处理程序逻辑注意事项)——我之所以提到这一点,是因为我对这个特定主题没有特定的权威。我自己只是一个寻求“启蒙”的学生:-)

【讨论】:

  • 我真的不喜欢对自动事件及其底层委托使用相同的标识符。恕我直言,应该允许使用事件名称来添加或删除订阅、将订阅列表设置为 null 或将其与 null 进行比较,或者调用事件,调用语法自动包括 null 检查我>。任何其他用法都应该要求使用代表的标识符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-10
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
相关资源
最近更新 更多