共用方式為


使用命令式程式代碼撰寫工作流程、活動和表達式

工作流程定義是已設定活動物件的樹狀結構。 此活動樹狀結構可以定義許多方式,包括手動編輯 XAML 或使用工作流程設計工具來產生 XAML。 不過,使用 XAML 並非必要專案。 您也可以以程序設計方式建立工作流程定義。 本主題提供使用程式代碼建立工作流程定義、活動和表達式的概觀。 如需使用程式代碼處理 XAML 工作流程的範例,請參閱 將工作流程和活動串行化至 XAML 和從 XAML

建立工作流程定義

工作流程定義可以藉由具現化活動類型的實例和設定活動對象的屬性來建立。 對於不包含子活動的活動,這可以使用幾行程式代碼來完成。

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

備註

本主題中的範例會使用 WorkflowInvoker 來執行範例工作流程。 如需叫用工作流程、傳遞自變數和可用之不同裝載選項的詳細資訊,請參閱 使用 WorkflowInvoker 和 WorkflowApplication

在此範例中,會建立由單 WriteLine 一活動組成的工作流程。 WriteLine 活動的 Text 參數已設置,並啟動工作流程。 如果活動包含子活動,則建構方法很類似。 以下範例使用一個Sequence活動,其中包含兩個WriteLine活動。

Activity wf = new Sequence
{
    Activities =
    {
        new WriteLine
        {
            Text = "Hello"
        },
        new WriteLine
        {
            Text = "World."
        }
    }
};

WorkflowInvoker.Invoke(wf);

使用物件初始化表達式

本主題的範例會使用物件初始化語法。 物件初始化語法在使用程式碼建立工作流程定義時會很有用,因為它會在工作流程中提供活動的階層式檢視,並顯示活動之間的關係。 當您以程式設計方式來建立工作流程時,不一定非得使用物件初始化語法。 下列範例在功能上等同於先前的範例。

WriteLine hello = new WriteLine();
hello.Text = "Hello";

WriteLine world = new WriteLine();
world.Text = "World";

Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);

WorkflowInvoker.Invoke(wf);

如需物件初始化表達式的詳細資訊,請參閱如何:使用物件初始化運算式來 初始化物件而不呼叫建構函式 (C# 程式設計手冊)如何:使用物件初始化表達式宣告物件

使用變數、常值和表達式

使用程式代碼建立工作流程定義時,請注意哪些程序代碼會在建立工作流程定義時執行,以及程式代碼在執行該工作流程實例時執行的一部分。 例如,下列工作流程的目的是要產生隨機數,並將其寫入控制台。

Variable<int> n = new Variable<int>
{
    Name = "n"
};

Activity wf = new Sequence
{
    Variables = { n },
    Activities =
    {
        new Assign<int>
        {
            To = n,
            Value = new Random().Next(1, 101)
        },
        new WriteLine
        {
            Text = new InArgument<string>((env) => "The number is " + n.Get(env))
        }
    }
};

執行此工作流程定義程式代碼時,會進行 對的呼叫 Random.Next ,並將結果儲存在工作流程定義中做為常值。 您可以叫用此工作流程的許多實例,而且全都會顯示相同的數位。 若要在工作流程執行期間發生隨機數產生,每次工作流程執行時都必須使用評估的表達式。 在下列範例中,Visual Basic 運算式會與 VisualBasicValue<TResult> 範例搭配使用。

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

上述範例中的表示式也可以使用CSharpValue<TResult>和 C# 運算式來實作。

new Assign<int>
{
    To = n,
    Value = new CSharpValue<int>("new Random().Next(1, 101)")
}

在叫用包含 C# 表達式的工作流程之前,必須先編譯 C# 表達式。 如果未編譯 C# 運算式, NotSupportedException 則會在叫用類似下列訊息的工作流程時擲回 : Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. 在 Visual Studio 中建立的工作流程大部分案例中,C# 表達式會自動編譯,但在某些情況下,例如程式代碼工作流程,必須手動編譯 C# 表達式。 如需如何編譯 C# 表達式的範例,請參閱 C# 表達式主題的程式碼工作流程中使用 C# 運算式一節。

VisualBasicValue<TResult>表示 Visual Basic 語法中的運算式,可用於運算式中的 r 值,而 CSharpValue<TResult> 代表 C# 語法中的運算式,可用於運算式中的 r 值。 每次執行包含的活動時,都會評估這些表達式。 表達式的結果會指派給工作流程變數 n,而工作流程中的下一個活動會使用這些結果。 要在執行時存取工作流程變數n的值,需要 。ActivityContext 這可以使用下列 Lambda 表達式來存取。

備註

請注意,這兩個程式碼範例都是使用 C# 作為程式語言,但是一個範例使用 VisualBasicValue<TResult>,另一個範例使用 CSharpValue<TResult>VisualBasicValue<TResult>CSharpValue<TResult> 可用於 Visual Basic 和 C# 專案。 根據預設,在工作流程設計工具中建立的表達式符合主控項目的語言。 在程式代碼中建立工作流程時,使用語言的選擇由工作流程作者決定。

在這些範例中,表達式的結果會指派給工作流程變數 n,而工作流程中的下一個活動會使用這些結果。 要在執行時存取工作流程變數n的值,需要 。ActivityContext 這可以使用下列 Lambda 表達式來存取。

new WriteLine
{
    Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}

如需 Lambda 表達式的詳細資訊,請參閱 Lambda 運算式 (C# 參考)Lambda 運算式 (Visual Basic)。

Lambda 運算式無法串行化為 XAML 格式。 如果嘗試使用 Lambda 運算式串行化工作流程, LambdaSerializationException 則會擲回 ,並出現下列訊息:「此工作流程包含程式碼中指定的 Lambda 運算式。 這些表達式不可序列化為 XAML。 若要讓您的工作流程 XAML 可串行化,請使用 VisualBasicValue/VisualBasicReference 或 ExpressionServices.Convert(lambda)。 這會將您的 Lambda 運算式轉換成運算式活動。」若要讓此表達式與 XAML 相容,請使用 ExpressionServicesConvert,如下列範例所示。

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}

VisualBasicValue<TResult>也可以使用 。 請注意,使用 Visual Basic 運算式時不需要 Lambda 運算式。

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    //Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
    Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}

執行時,Visual Basic 表達式會被編譯成 LINQ 表達式。 上述兩個範例都可以序列化為 XAML,但如果序列化的 XAML 要在工作流程設計工具中檢視和編輯,請對您的運算式使用 VisualBasicValue<TResult>。 使用 ExpressionServices.Convert 的串行化工作流程可以在設計工具中開啟,但表達式的值會是空白的。 如需將工作流程串行化為 XAML 的詳細資訊,請參閱 將工作流程和活動串行化至 XAML

文字表達式和參考型別

在工作流程中,Literal<T> 活動用來表示文字表達式。 下列 WriteLine 活動在功能上相同。

new WriteLine
{
    Text = "Hello World."
},
new WriteLine
{
    Text = new Literal<string>("Hello World.")
}

使用任何參考型別初始化常值表示式都是無效的,除了 String。 在下列範例中,Assign 活動的 Value 屬性會使用 List<string> 來初始化為常值表達式。

new Assign
{
    To = new OutArgument<List<string>>(items),
    Value = new InArgument<List<string>>(new List<string>())
},

驗證包含此活動的工作流程時,會傳回下列驗證錯誤:「字面值僅支持值類型和不可變類型 System.String。」 無法將 System.Collections.Generic.List'1[System.String] 類型當做常值使用。如果叫用工作流程, InvalidWorkflowException 則會擲回 ,其中包含驗證錯誤的文字。 這是驗證錯誤,因為使用參考型別建立字面值表達式並不會為每個流程執行個體建立參考型別的新實例。 若要解決此問題,請將字面表達式取代為建立並傳回參考型別的新實例。

new Assign
{
    To = new OutArgument<List<string>>(items),
    Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))
},

如需表達式的詳細資訊,請參閱 表達式

使用表達式和 InvokeMethod 活動調用物件的方法

活動 InvokeMethod<TResult> 可用來叫用 .NET Framework 中類別的靜態和實例方法。 在本主題的上一個範例中,會使用 Random 類別產生隨機數。

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

活動 InvokeMethod<TResult> 也可用來呼叫 Next 類別的 Random 方法。

new InvokeMethod<int>
{
    TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),
    MethodName = "Next",
    Parameters =
    {
        new InArgument<int>(1),
        new InArgument<int>(101)
    },
    Result = n
}

由於 Next 不是靜態方法,因此會為 Random 屬性提供 類別的TargetObject實例。 在此範例中,會使用 Visual Basic 運算式建立新的實例,但也可能先前建立並儲存在工作流程變數中。 在此範例中,使用 Assign<T> 活動而非 InvokeMethod<TResult> 活動會更簡單。 如果由 Assign<T>InvokeMethod<TResult> 活動最終叫用的方法呼叫是長時間執行的,則 InvokeMethod<TResult> 具有優勢,因為它有 RunAsynchronously 屬性。 當這個屬性設定為 true時,叫用的方法將會以異步方式執行工作流程。 如果其他活動是平行的,當方法以異步方式執行時,它們將不會遭到封鎖。 此外,如果要叫用的方法沒有傳回值,則 InvokeMethod<TResult> 為叫用方法的適當方式。

爭論和動態活動

工作流程定義會在程式代碼中建立,方法是將活動組合成活動樹狀結構,並設定任何屬性和自變數。 現有的自變數可以系結,但無法將新的自變數新增至活動。 這包括傳遞至根活動的流程參數。 在命令式程式代碼中,工作流程自變數會指定為新 CLR 類型的屬性,並在 XAML 中使用 x:Classx:Member來宣告它們。 由於當工作流程定義建立為記憶體內部物件的樹狀結構時,不會建立新的CLR類型,因此無法新增自變數。 不過,可以將自變數加入至 DynamicActivity。 在此範例中,會建立一個 DynamicActivity<TResult>,它接受兩個整數參數、將它們加在一起,並傳回結果。 DynamicActivityProperty會針對每個自變數建立 ,並將作業的結果指派給 ResultDynamicActivity<TResult>自變數。

InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();

DynamicActivity<int> wf = new DynamicActivity<int>
{
    Properties =
    {
        new DynamicActivityProperty
        {
            Name = "Operand1",
            Type = typeof(InArgument<int>),
            Value = Operand1
        },
        new DynamicActivityProperty
        {
            Name = "Operand2",
            Type = typeof(InArgument<int>),
            Value = Operand2
        }
    },

    Implementation = () => new Sequence
    {
        Activities =
        {
            new Assign<int>
            {
                To = new ArgumentReference<int> { ArgumentName = "Result" },
                Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
            }
        }
    }
};

Dictionary<string, object> wfParams = new Dictionary<string, object>
{
    { "Operand1", 25 },
    { "Operand2", 15 }
};

int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);

如需動態活動的詳細資訊,請參閱 在運行時間建立活動

整理後的活動

動態活動是定義活動的方法之一,其中包含使用程式代碼的自變數,但活動也可以在程式代碼中建立並編譯成類型。 您可以建立衍生自 CodeActivity的簡單活動,以及衍生自 AsyncCodeActivity的異步活動。 這些活動可以有自變數、傳回值,以及使用命令式程式代碼來定義其邏輯。 如需建立這些活動類型的範例,請參閱 CodeActivity基類建立異步活動

衍生自 NativeActivity 的活動可以使用命令式程式代碼來定義其邏輯,也可以包含定義邏輯的子活動。 他們也可以完整存取執行時環境的功能,例如建立書籤。 如需建立 NativeActivity型活動的範例,請參閱 NativeActivity 基類如何:建立活動,以及 使用原生活動自定義複合 範例。

衍生自 Activity 的活動僅透過使用子活動來定義其邏輯。 這些活動通常是使用工作流程設計工具所建立,但也可以使用程式代碼來定義。 在下列範例中, Square 定義衍生自 Activity<int>的活動。 活動Square具有一個名為InArgument<T>的單一Value,並使用Sequence屬性指定的Implementation活動來定義其邏輯。 活動 Sequence 包含 WriteLine 活動和 Assign<T> 活動。 這三個活動會一起實作 Square 活動的邏輯。

class Square : Activity<int>
{
    [RequiredArgument]
    public InArgument<int> Value { get; set; }

    public Square()
    {
        this.Implementation = () => new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))
                },
                new Assign<int>
                {
                    To = new OutArgument<int>((env) => this.Result.Get(env)),
                    Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))
                }
            }
        };
    }
}

在下列範例中,會使用 Square 調用由單一 WorkflowInvoker 活動組成的工作流程定義。

Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};
int result = WorkflowInvoker.Invoke(new Square(), inputs);
Console.WriteLine("Result: {0}", result);

叫用工作流程時,主控台會顯示下列輸出:

平方數值:5結果:25