该使用什么?委托(delegate)、事件(event)还是Func<T>?

9
我希望为类库中的对象提供“输出”消息的功能,而无需担心如何输出。该类库可用于控制台应用程序、WinForm或WPF窗口应用程序或Web页面。
起初我决定使用委托来处理此问题。我使用了类来实现它,但是当我尝试将委托放入每个对象都继承的接口时,编译器不喜欢这个委托。
后来我发现我可以将委托移出接口,然后就能编译了,但我不确定这是否实现了我想要做的事情。这似乎也有点笨拙,所以我想问问你们是否有不同的想法来实现这个功能...
接口:
namespace Test
{
  using System;
  using System.Xml.Linq;

  public interface IAction
  {
    DisplayMessageDelegate DisplayMessage(string message);
    void Execute();
    XElement Serialize(XName elementName);
  }

  public delegate void DisplayMessageDelegate(string message);
}

从那里开始,我不确定如何实现这种行为:(顺便说一下,我知道这段代码无法编译...)

public class ActionClass1 : IAction
{
  // Other methods not shown...
  void Execute()
  {
    if (this.DisplayMessage != null)
    {
      this.DisplayMessage(“Hello”);
    }
  }
}

public class ConsoleClass
{
  ActionClass1 class1 = new ActionClass1();
  class1.DisplayMessage = { x =>  Console.WriteLine(x); };
}

public class WinFormClass
{
  ActionClass1 class1 = new ActionClass1();
  Class1.DisplayMessage = {x => DisplayTextBox.Text = x; };
}
6个回答

15
如果您想要连接多个委托来响应单个Execute调用,我肯定会使用一个event。如果您只想连接一个动作,请使用一个Action或一个Func委托。
对于您的示例,其中一个Action委托应该可以工作。在您的情况下,它将是Action<string>,因为您的委托接受一个字符串参数。 Action只是一个带有零个或更多参数并返回void的委托。看起来你没有返回任何内容,所以我建议使用Action
只有当您的委托需要返回东西时才使用Func<TResult>FuncAction之间的区别在于Func委托具有返回类型,而Action委托没有。这两个委托都有泛型版本,可以接受多达16个参数。
如果您的委托需要超过16个参数,您可能需要重新考虑设计 :)

1
需要注意的是,如果您需要比params、ref/out等更复杂的内容...Action/Func就无法胜任。另外一个“好处”是您可以适当地命名参数(智能感知)。话虽如此,我也会使用Action<string>。 - Jake

7

您可以使用 Action<string> 来实现这一点。

不建议使用 Func<T>,因为它定义了一个不带参数但返回单个类型为 T 的值的委托。另一方面,Action<T> 是一个接受单个类型为 T 的参数的委托。

我建议尝试:

public interface IAction
{
    Action<string> DisplayMessage { get; set; }

    void Execute();
    XElement Serialize(XName elementName);
}

一旦您完全实现了此接口,您可以通过以下方式使用它:

public class ConsoleClass
{
    public void SomeMethod()
    {
        ActionClass1 class1 = new ActionClass1();
        class1.DisplayMessage = x => Console.WriteLine(x);
    }
}

或者:

public class ConsoleClass
{
    public void SomeMethod()
    {
        ActionClass1 class1 = new ActionClass1();
        class1.DisplayMessage = this.Print;
    }

    private void Print(string message)
    {
        Console.WriteLine(message);
    }
}

您可以使用事件来完成同样的事情,但是我会对此提出疑问。您的API描述的是应该发生的操作,而不是正在发生并由您响应的事件 - 因此,我不建议使用事件。


1

你的接口定义有误。你需要像这样指定:

namespace Test
{
  using System;
  using System.Xml.Linq;

  public interface IAction
  {
    DisplayMessageDelegate DisplayMessage { get; set; };
    void Execute();
    XElement Serialize(XName elementName);
  }

  public delegate void DisplayMessageDelegate(string message);
}

然后实现接口。


1

这里

DisplayMessageDelegate DisplayMessage(string message);

你需要描述一个接受字符串并返回DisplayMessageDelegate的方法。

 event DisplayMessageDelegate DisplayMessage;

取而代之。


0

改为:

DisplayMessageDelegate DisplayMessage(string message);

执行:

event Action<string> DisplayMessage;

然后像平常一样使用DisplayMessage,事件是委托。


0

个人认为,最好拥有一个抽象的基类,定义一个名为DisplayMessage的抽象方法,然后通过从它派生来扩展该基类,以更改消息显示方式的行为。


我考虑过......但我刻意忽略了这个事实(为了简化问题),即这些派生类将在一个List<IAction>集合中,我将使用ForEach循环来执行每个项目的Execute方法,并根据当前运行的应用程序类型设置“DisplayMessage”... - Julian Easterling

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