C# C语言设计:事件的显式接口实现

C# C语言设计:事件的显式接口实现,c#,events,interface,explicit-implementation,C#,Events,Interface,Explicit Implementation,关于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 cl

关于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;
}
我将得到以下编译器错误:

错误CS0071:事件的显式接口实现必须使用事件访问器语法

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


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

有趣的问题。我翻了翻语言笔记档案,发现这个决定是在1999年10月13日做出的,但笔记并没有为这个决定提供理由


从我的头脑中,我看不到任何理论或实践上的原因,为什么我们不能有类似于字段的显式实现的事件。我也看不出有任何理由特别需要这样做。这可能仍然是未知的谜团之一。

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

例如,这将触发错误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() { }
}

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

然而,我想我可能会对此作出回应:

“从我的头脑中,我看不到任何理论或实践上的理由,为什么我们不能有类似领域的明确实施的事件。我也看不到任何理由,为什么我们特别需要这样做。这可能仍然是未知的奥秘之一。” -埃里克·利珀特


在第二版《程序员C#入门》第23章中,Eric Gunnerson写道:

“[I]如果单击按钮时还希望调用另一个类,则可以使用+=运算符,如下所示:

interface IFoo {
  int Value { get; set; }
}
按钮.Click+=新建按钮.ClickHandler(其他方法调用)

不幸的是,如果另一个类不小心,它可能会执行以下操作:

按钮。单击=新建按钮。单击处理程序(其他方法调用)

这将是不好的,因为这将意味着我们的ButtonHandler将被取消挂钩,只调用新方法。”

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


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

我想补充更多,但我太尊重作者了,没有他的允许我不能这么做。我建议找一本这本书,一般来说,我会推荐Eric Gunnerson的任何作品(博客等…)


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

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

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;
}

在这里,您甚至不能通过首先向上转换到接口来引发事件,因为只能从实现类内部引发事件。因此,对于显式实现的事件,要求访问器语法似乎非常合理。

这就是为什么我有时仍然访问。。。还有什么地方人们会用“我不知道”作为一个好答案。似乎原因可能是,除非显式实现的事件与显式实现的方法具有不同的可见性规则,否则您永远无法引发这样的事件,正如我在回答中所解释的那样……我认为Andreas Huber的回答是合理的。+1表示正确的实现,然而,这不是问题所在。人们往往在问题的最底层找到真正的问题。有点像你在告诉你父母你被拘留之前想解释你为什么这么做。也许不是这个问题的明确答案,但这可能为我节省了一个小时,让我想办法解决问题。回答得好。在我看来似乎有道理。谢谢,我同意。类似字段的事件由三部分组成:
add
访问器、
remove
访问器,以及对委托类型的底层生成字段的私有访问。使用
MyEvent!=null
local=MyEvent
MyEvent(发送方,args)
等。但是,只有
add
remove
访问器是接口“契约”的一部分。在C#6.0(2015)中,可以创建一个仅为
get
的自动属性。然后可以从同一类的构造函数中为其赋值。它真正分配给基础字段。例如
密封类Foo{public int Value{get;}public Foo(){Value=42;}}
。如果您试图通过显式接口实现来实现这一点,C#编译器将允许它,但您不能再在构造函数中赋值(backing字段不是<