Инкапсуляция Action‹T› и Func‹T›?

Я пытаюсь создать дизайн для какого-то интерфейса 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();

person Alpha    schedule 25.11.2010    source источник
comment
В качестве пояснения: моя цель - иметь абстрактный Execute в ActionBase и избегать создания ActionWithResultBase и ActionWithoutResultBase.   -  person Alpha    schedule 25.11.2010


Ответы (4)


Если вам нужно облегченное решение, то самым простым вариантом будет написать два конкретных класса. Один будет содержать свойство типа 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; }
person Tomas Petricek    schedule 25.11.2010
comment
Спасибо. Я знаю, что долго ждал, прежде чем отметить ваш ответ, но я действительно хотел посмотреть, не придумал ли кто-нибудь решение. Даже если ваш подход не совсем решает проблему, это действительно хороший ответ и показывает хороший и приятный обходной путь решения этой проблемы. - person Alpha; 31.05.2011

Следующие интерфейсы должны сработать — они, по сути, копируют шаблон 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() { //... }
}
person cordialgerm    schedule 25.11.2010

Вы знаете, что можете игнорировать возвращаемое значение метода, верно? Вам не обязательно его использовать.

person annakata    schedule 25.11.2010
comment
Да, вы правы в этом, но я не думаю, что это будет что-то разумное с точки зрения дизайна. Кроме того, кто-то предложил мне всегда возвращать bool для тех невозвратов, которые просто указывали бы на то, что операция прошла успешно, но я также думаю, что это неправильно. Если операция не должна возвращать значение, зачем ее заставлять? - person Alpha; 25.11.2010
comment
@Alpha - я согласен с вами, что если операция естественным образом не возвращает что-то, она не должна возвращаться. Возвращать true неправильно, потому что, вообще говоря, вы не должны возвращать false, вы должны генерировать исключение. Однако я до сих пор не понимаю вашей цели дизайна - ваша цель, похоже, основана на синтаксическом сахаре, и я не вижу, чего вы добиваетесь, чего вы не могли бы просто решить с помощью Action и Func. Я думаю, вам нужно объяснить, в чем проблема с ними? - person annakata; 25.11.2010

а как насчет простого:

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

    public object Execute(params object[] p)
    {
        return del.DynamicInvoke(p);
    }
}
person Dean Chalk    schedule 25.11.2010
comment
Также думал о чем-то подобном, но моя проблема в том, что во время компиляции я не узнаю, какой тип возвращается. Я всегда мог бы сделать ActionExecuter‹T› и заставить Execute вернуть T, но это превосходит ситуации, когда мне не нужно ничего возвращать. - person Alpha; 25.11.2010