如何在C#中向接口添加委托

112

我需要在我的类中使用一些委托。

我想使用接口来“提醒”我设置这些委托。

如何实现?

我的类看起来像这样:

public class ClsPictures : myInterface
{
    // Implementing the IProcess interface
    public event UpdateStatusEventHandler UpdateStatusText;
    public delegate void UpdateStatusEventHandler(string Status);

    public event StartedEventHandler Started;
    public delegate void StartedEventHandler();
}

我需要一个接口来强制执行这些委托:

public interface myInterface
{
   // ?????
}
6个回答

172

这些是声明委托类型。它们不属于一个接口。但使用这些委托类型的事件可以放在接口中:

public delegate void UpdateStatusEventHandler(string status);
public delegate void StartedEventHandler();

public interface IMyInterface
{       
    event UpdateStatusEventHandler StatusUpdated;    
    event StartedEventHandler Started;
}
实现代码不会(也不应该)重新声明委托类型,就像它不会重新声明接口中使用的任何其他类型一样。

这对我来说根本行不通。我确定我已经按照这种方式做了一切,但是我的接口中的处理程序没有被识别。实现接口的类抱怨该类与System.EventHandler接口返回类型不匹配。 - Chucky
1
@Chucky:听起来你应该提出一个新问题,并附上一个简短但完整的示例来演示问题。我给出的答案确实有效。 - Jon Skeet
7
我以前并不知道委托与类拥有相同的访问级别。我一直认为它们需要封装在一个类中。 - Purusartha
8
@Purusartha说的不是可访问性,而只是委托被视为类型(事实上是类),就像接口等一样。 - Jon Skeet

38

自 .NET 3.5 开始,您还可以使用 System.Action 委托,而无需声明自己的类型。

这将导致以下接口:

public interface myInterface
{       
   // Implementing the IProcess interface
   event Action<String> UpdateStatusText;

   event Action Started;
}

1
我认为Action/Func比传统的委托类型更加优雅(且易读),而且你可以在接口中定义它们。 - chrnola
5
这个回答没有解决问题(如何实现我的接口)。 - lharper71
3
使用“Action”委托在接口中存在的问题是,从类型参数上不太容易看出参数表示什么,如果只有一个参数可能还好,但如果有多个参数,很快就会变得混乱。这种情况下最好使用委托类型。 - David Clarke
2
我同意David的看法,假设你有Func<string, int, Task> ExecuteQuery { get; },那么显然string值是什么,但是int是什么呢?通过使用delegate,你可以更好地定义它,就像这样:public delegate Task ExecuteQueryHandler(string query, int timeout);现在每个参数都变得100%清晰明了。 - Middas

19

只需将委托公开为属性即可

public delegate void UpdateStatusEventHandler(string status);
public delegate void StartedEventHandler();

public interface IMyInterface
{       
    UpdateStatusEventHandler StatusUpdated {get; set;}    
    StartedEventHandler Started {get; set;}
}

7

Jon Skeet的回答是正确的,我只想添加一点注释。

接口并不是为了“提醒”您要做什么,或者在类中包含什么。接口是抽象的手段,用于面向对象编程和设计方法。除非您希望在程序的其他地方看到某些具体类实例作为接口(抽象),否则您可能根本不需要接口声明。

如果您想在项目中强制执行一些编码标准,则可以尝试使用代码分析工具(例如Visual Studio)-它们允许扩展,您可以将其合并以添加自己的代码分析规则。

使用代码分析,如果您“忘记”添加委托(虽然我不明白忘记它的意义,因为如果不使用委托,则不需要它),您将收到警告/错误。


2
你可能是对的,但有时候即使有好的文档,也会有一些假设很难在一段时间后记住。我尝试重用我的代码,但有时候我无法记住什么是核心,什么不是(当涉及到委托时 - 对于我来说很难看到大局...) - Asaf

2

您的一条评论提到了事件处理程序的返回类型。您更关心处理程序的类型还是从事件返回的数据?如果是后者,则可能会有所帮助。如果不是,则此解决方案不够,但可能有助于使您更接近您想要的结果。

您只需要在接口和实现中将事件处理程序声明为通用事件处理程序,就可以自定义返回结果。

您的具体类应如下所示:

public class ClsPictures : myInterface
{
    // Implementing the IProcess interface
    public event EventHandler<UpdateStatusEventArgs> UpdateStatusText;
    //no need for this anymore: public delegate void UpdateStatusEventHandler(string Status);

    public event EventHandler<StartedEventArgs> Started;
    //no need for this anymore: public delegate void StartedEventHandler();
}

您的界面将如下所示:

public interface myInterface
{
   event EventHandler<StartedEventArgs> Started;
   event EventHandler<UpdateStatusEventArgs> UpdateStatusText;
}

现在事件参数返回您的类型,您可以将它们钩入任何您定义的处理程序中。
参考文献: https://msdn.microsoft.com/zh-cn/library/edzehd2t(v=vs.110).aspx

1
您派生类中继承的接口将提醒您定义并链接在其中声明的内容。
但是,您可能还想显式地使用它,并且仍需要将其关联到一个对象。
例如,使用控制反转模式:
class Form1 : Form, IForm {
   public Form1() {
     Controls.Add(new Foo(this));
   }

   // Required to be defined here.
   void IForm.Button_OnClick(object sender, EventArgs e) {
     ...
     // Cast qualifier expression to 'IForm' assuming you added a property for StatusBar.
     //((IForm) this).StatusBar.Text = $"Button clicked: ({e.RowIndex}, {e.SubItem}, {e.Model})";
   }
 }

你可以尝试像这样的东西。
interface IForm {
  void Button_OnClick(object sender, EventArgs e);
}


class Foo : UserControl {
  private Button btn = new Button();

  public Foo(IForm ctx) {
     btn.Name = "MyButton";
     btn.ButtonClick += ctx.Button_OnClick;
     Controls.Add(btn);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接