共用方式為


使用命令式程式碼撰寫工作流程、活動和運算式

工作流程定義是配置之活動物件的樹狀。 有很多方式可定義這個活動的樹狀結構,包含手動編輯 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# 運算式未編譯,以類似下列方式來叫用工作流程時,將會擲回 NotSupportedExceptionExpression 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-value),而 CSharpValue<TResult> 是以 C# 語法表示的運算式,可用來當做運算式中的右值。 每次執行包含的活動時會評估這些運算式。 運算式的結果會指派至工作流程變數 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 的活動可以藉由使用子活動,只定義其邏輯。 這些活動通常是使用工作流程設計工具來建立,但也可以使用程式碼來定義。 在下列範例中,會定義衍生自 SquareActivity<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