封装 Action<T> 和 Func<T>?

12

我正在尝试设计某种IExecutable接口。我不会详细介绍,但重点是我有几个需要从基类执行的操作。它们可能需要不同的参数(没有什么大不了的),并且可能/可能不返回值。

到目前为止,这是我的设计:

public abstract class ActionBase
{
    // ... snip ...
}

public abstract class ActionWithResultBase<T>: ActionBase
{
    public abstract T Execute();
}

public abstract class ActionWithoutResultBase: ActionBase
{
    public abstract void Execute();
}

到目前为止,我所有的具体操作都需要是ActionWithResultBase或ActionWithoutResult基类的子项,但我真的不喜欢这样做。如果我可以将Execute的定义移动到ActionBase中,考虑到具体类可能返回值也可能不返回值,那么我就实现了我的目标。
有人告诉我可以使用Func和Action来实现,对此我完全同意,但我找不到一种方法将其放入一个单一的类中,以便调用者知道该操作是否会返回值。
简而言之,我想做的是:
// Action1.Execute() returns something.
var a = new Action1();
var result = a.Execute();

// Action2.Execute() returns nothing.
var b = new Action2();
b.Execute();

澄清一下:我的目标是在ActionBase中拥有一个抽象的Execute,并避免创建ActionWithResultBase和ActionWithoutResultBase。 - Alpha
4个回答

11

如果你想要一个轻量级的解决方案,那么最简单的选择是编写两个具体类。其中一个将包含类型为Action的属性,另一个将包含类型为Func<T>的属性:

public class ActionWithResult<T> : ActionBase { 
  public Func<T> Action { get; set; } 
}

public class ActionWithoutResult : ActionBase {
  public Action Action { get; set; }
}

那么,您可以像这样构造这两种类型:

var a1 = new ActionWithResult<int> { 
  CanExecute = true,
  Action = () => { 
    Console.WriteLine("hello!");
    return 10; 
  }
}
如果您不想使Action属性可读/写,那么您可以将操作委托作为参数传递给构造函数,并使该属性只读。
C#需要使用两个不同的委托类型来表示函数和操作,这一点相当令人困扰。人们使用的一种解决方法是定义一个表示“无返回值”的类型Unit,并将其用于代替void。然后您的类型将仅为Func<T>,并且您可以使用Func<Unit>代替Action。这个Unit类型可能长这样:
public class Unit {
  public static Unit Value { get { return null; } }
}

要创建一个Func<Unit>值,你需要编写:

Func<Unit> f = () => { /* ... */ return Unit.Value; }

谢谢。我知道在标记您的答案之前等了很久,但我真的想看看是否有人提出了解决方案。即使您的方法并没有完全解决问题,它确实是一个好答案,并展示了一个不错的解决此问题的变通方法。 - Alpha

2
以下接口应该能解决问题 -- 本质上是复制 Nullable 模式。
public interface IActionBase
{
       bool HasResult { get; }
       void Execute() { }
       object Result { get; }
}

public interface IActionBase<T> : IActionBase
{
       new T Result { get; }
}

public sealed class ActionWithReturnValue<T> : IActionBase<T>
{
       public ActionWithReturnValue(Func<T> action) {  _action = action; }
       private Func<T> _action;

       public bool HasResult { get; private set; }
       object IActionBase.Result { get { return this.Result; } }
       public T Result { get; private set; }
       public void Execute()
       {
            HasResult = false;
            Result = default(T);
            try 
            { 
                 Result = _action();
                 HasResult = true;
             }
            catch
            {
                HasResult = false;
                Result = default(T);   
            }  
       }

}

public sealed class ActionWithoutReturnValue : IActionBase
{
      public bool HasResult { get { return false; } }
      object IActionBase.Result { get { return null; } }
      public void Execute() { //... }
}

0

你知道你可以忽略一个方法的返回值吗?你不必须使用它。


是的,你说得没错,但我认为这不是设计上应该有的东西。此外,有人建议我对于那些没有返回值的操作始终返回布尔值,以表示操作成功,但我也认为这是不正确的。如果操作不应该返回值,为什么要强制它呢? - Alpha
@Alpha - 我同意你的观点,如果一个操作没有自然的返回值,就不应该返回什么。返回"true"不正确,因为通常不应该返回"false",而应该抛出异常。然而我仍然不理解你的设计目标 - 你的目标似乎基于语法糖,我看不出你所做的事情可以通过Action和Func来解决。我认为你需要解释一下它们存在什么问题? - annakata

0

有没有什么简单的东西:

public class ActionExecuter
{
    private MulticastDelegate del;
    public ActionExecuter(MulticastDelegate del)
    {
        this.del = del;
    }

    public object Execute(params object[] p)
    {
        return del.DynamicInvoke(p);
    }
}

我也考虑过类似的事情,但我的问题是在编译时我不知道返回哪种类型。我可以始终创建ActionExecuter<T>并使Execute返回T,但这会打败我不需要返回任何内容的情况。 - Alpha

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