Поделиться через


Использование делегатов действий

Делегаты действий позволяют создателям действий предоставлять обратные вызовы с определенными сигнатурами, для которых пользователи действия могут предоставить обработчики, основанные на действиях. Доступны два типа делегатов действия: ActivityAction<T> используется для определения делегатов действий, которые не возвращают значение, а ActivityFunc<TResult> используется для определения делегатов действий, которые возвращают значение.

Делегаты действий удобно использовать в тех случаях, когда дочерние действия должны быть ограничены необходимостью использования определенной сигнатуры. Например, действие While может содержать любой тип дочернего действия без ограничений, но текстом действия ForEach<T> является ActivityAction<T>; дочернее действие, в конце концов выполняемое ForEach<T>, должно иметь InArgument<T> того же типа элементов коллекции, который перечисляет ForEach<T>.

Использование ActivityAction

Несколько действий платформа .NET Framework 4.6.1 используют действия, такие как Catch действие и ForEach<T> действие. В каждом из случаев действие операции представляет каталог, в котором автор рабочего процесса указывает операцию, обеспечивающую желаемое поведение, при составлении рабочего процесса с использованием одной из подобных операций. В следующем примере для отображения текста в окне консоли использовано действие ForEach<T>. Текст ForEach<T> определяется с использованием ActivityAction<T>, совпадающим с типом ForEach<T>, указанным в виде строки. Действие WriteLine, указанное в свойстве Handler, имеет аргумент Text, привязанный к строковым значениям в коллекции, прохождение по которой производится действием ForEach<T>.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

Activity wf = new ForEach<string>
{
    Body = new ActivityAction<string>
    {
        Argument = actionArgument,
        Handler = new WriteLine
        {
            Text = new InArgument<string>(actionArgument)
        }
    }
};

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

Аргумент actionArgument используется для передачи отдельных элементов в коллекции на WriteLine. При вызове рабочего процесса на консоль выводятся следующие данные.

HelloWorld.

В примерах в этом разделе используется синтаксис инициализации объектов. Синтаксис инициализации объектов можно использовать для создания определений рабочих процессов в коде, поскольку он обеспечивает иерархическое представление действий в рабочем процессе и показывает связи между действиями. При программном создании рабочих процессов нет необходимости использовать синтаксис инициализации объектов. Следующий пример функционально эквивалентен предыдущему примеру.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

WriteLine output = new WriteLine();
output.Text = new InArgument<string>(actionArgument);

ActivityAction<string> body = new ActivityAction<string>();
body.Argument = actionArgument;
body.Handler = output;

ForEach<string> wf = new ForEach<string>();
wf.Body = body;

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

Дополнительные сведения об инициализаторах объектов см. в статье "Практическое руководство. Инициализация объектов без вызова конструктора (руководство по программированию на C#) и практическое руководство. Объявление объекта с помощью инициализатора объектов (Visual Basic)".

В следующем примере действие TryCatch используется в рабочем процессе. ApplicationException вызывается рабочим процессом и обрабатывается действием Catch<TException>. Обработчик Catch<TException> действия является WriteLine действием, а сведения об исключении передаются к нему с помощью ex DelegateInArgument<T>.

DelegateInArgument<ApplicationException> ex = new DelegateInArgument<ApplicationException>()
{
    Name = "ex"
};

Activity wf = new TryCatch
{
    Try = new Throw()
    {
        Exception = new InArgument<Exception>((env) => new ApplicationException("An ApplicationException was thrown."))
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Argument = ex,
                Handler = new WriteLine()
                {
                    Text = new InArgument<string>((env) => ex.Get(env).Message)
                }
            }
        }
    },
    Finally = new WriteLine()
    {
        Text = "Executing in Finally."
    }
};

При создании пользовательского действия, которое определяет действие ActivityAction<T>, используйте InvokeAction<T> для моделирования вызова этого действия ActivityAction<T>. В этом примере определяется пользовательское действие WriteLineWithNotification. Это действие состоит из объекта Sequence, который содержит действие WriteLine, сопровождаемое действием InvokeAction<T>, которое вызывает действие ActivityAction<T>, принимающее один строковый аргумент.

public class WriteLineWithNotification : Activity
{
    public InArgument<string> Text { get; set; }
    public ActivityAction<string> TextProcessedAction { get; set; }

    public WriteLineWithNotification()
    {
        this.Implementation = () => new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = new InArgument<string>((env) => Text.Get(env))
                },
                new InvokeAction<string>
                {
                    Action = TextProcessedAction,
                    Argument = new InArgument<string>((env) => Text.Get(env))
                }
            }
        };
    }
}

Когда создается рабочий процесс с помощью действия WriteLineWithNotification, создатель процесса задает нужную пользовательскую логику в свойстве Handler операции действия. В этом примере создается рабочий процесс, использующий действие WriteLineWithNotification, а действие WriteLine используется как Handler.

// Create and invoke the workflow without specifying any activity action
// for TextProcessed.
Activity wf = new WriteLineWithNotification
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

// Output:
// Hello World.

// Create and Invoke the workflow with specifying an activity action
// for TextProcessed.
DelegateInArgument<string> msg = new DelegateInArgument<string>();
Activity wf2 = new WriteLineWithNotification
{
    Text = "Hello World with activity action.",
    TextProcessedAction = new ActivityAction<string>
    {
        Argument = msg,
        Handler = new WriteLine
        {
            Text = new InArgument<string>((env) => "Handler of: " + msg.Get(env))
        }
    }
};

// Invoke the workflow with an activity action specified
WorkflowInvoker.Invoke(wf2);

// Output:
// Hello World with activity action.
// Handler of: Hello World with activity action.

Существует несколько универсальных версий InvokeAction<T> и ActivityAction<T> для передачи одного или нескольких аргументов.

Использование ActivityFunc

ActivityAction<T> используется в случае, когда действие не возвращает результирующего значения, а ActivityFunc<TResult> используется, когда результирующее значение возвращается. При создании пользовательского действия, которое определяет действие ActivityFunc<TResult>, используйте InvokeFunc<TResult> для моделирования вызова этого действия ActivityFunc<TResult>. В следующем примере определяется действие WriteFillerText . Для ввода текста наполнения задается действие InvokeFunc<TResult>, которое принимает целочисленный аргумент и имеет строковый результат. После извлечения текст наполнения выводится на экран консоли с помощью действия WriteLine.

public class WriteFillerText : Activity
{
    public ActivityFunc<int, string> GetText { get; set; }
    public InArgument<int> Quantity { get; set; }

    Variable<string> text = new Variable<string>
    {
        Name = "Text"
    };

    public WriteFillerText()
    {
        this.Implementation = () => new Sequence
        {
            Variables =
            {
                text
            },
            Activities =
            {
                new InvokeFunc<int, string>
                {
                    Func = GetText,
                    Argument = new InArgument<int>((env) => Quantity.Get(env)),
                    Result = new OutArgument<string>(text)
                },
                new WriteLine
                {
                    Text = new InArgument<string>(text)
                }
            }
        };
    }
}

Для ввода текста необходимо использовать действие, которое принимает аргумент int и имеет строковый результат. В этом примере показано действие TextGenerator, удовлетворяющее этим требованиям.

public class TextGenerator : CodeActivity<string>
{
    public InArgument<int> Quantity { get; set; }
    public InArgument<string> Text { get; set; }

    protected override string Execute(CodeActivityContext context)
    {
        // Provide a quantity of Random Text
        int q = Quantity.Get(context);
        if (q < 1)
        {
            q = 1;
        }

        string text = Text.Get(context) + " ";
        StringBuilder sb = new StringBuilder(text.Length * q);
        for (int i = 0; i < q; i++)
        {
            sb.Append(text);
        }

        return sb.ToString();
    }
}

Чтобы использовать действие TextGenerator с действием WriteFillerText, задайте его в виде Handler.

DelegateInArgument<int> actionArgument = new DelegateInArgument<int>();

Activity wf = new WriteFillerText
{
    Quantity = 5,
    GetText = new ActivityFunc<int, string>
    {
        Argument = actionArgument,
        Handler = new TextGenerator
        {
            Quantity = new InArgument<int>(actionArgument),
            Text = "Hello World."
        }
    }
};

WorkflowInvoker.Invoke(wf);