Skip to content

5.5.2.2.1. Data Flow

Mikołaj Milewski edited this page Nov 12, 2026 · 1 revision

Data Flow

An object flow carries typed tokens between Activity nodes. In UML diagrams it is normally shown as a directed edge, optionally labeled with the carried object. In Stateflows, declare its token type with AddFlow<TToken>(target).

Producing and consuming tokens

Action nodes publish values with Output or OutputRange; downstream actions read the values selected for their activation with GetTokensOfType<TToken>().

.AddAction(
    "CreateOrder",
    async c => c.Output(new Order("ORD-42", [new Item("Book", 12m, 2)])),
    node => node.AddFlow<Order>("CalculateTotal"))
.AddAction(
    "CalculateTotal",
    async c =>
    {
        var order = c.GetTokensOfType<Order>().Single();
        c.Output(order.Items.Sum(item => item.Price * item.Quantity));
    },
    node => node.AddFlow<decimal>("DisplayTotal"))
.AddAction(
    "DisplayTotal",
    async c =>
    {
        var total = c.GetTokensOfType<decimal>().Single();
        await display.ShowAsync(total, c.CancellationToken);
    })

The TToken on the flow selects the source token type. Without a transformation, that is also the type delivered to the target. A transformation can change it: for example, AddFlow<Order>("DisplayTotal", f => f.AddTransformation(async c => c.Token.Items.Sum(item => item.Price * item.Quantity))) delivers a decimal instead of an Order. The target must consume the transformed type. A guard that returns false prevents that token from traversing the flow.

Tokens are separate from control flow. Use AddControlFlow for sequencing without data. See Tokens, Flows, Input Pin, and Output Pin.

Crossing object flows

Activity node names are globally unique, including across nested scopes. AddFlow<TToken>("name") resolves the registered name directly; if the target is outside the source's containing scope, the flow crosses that boundary automatically. Guards, transformations, weights, and implicit joins keep their normal semantics.

Ordinary structured scopes allow both directions. Incoming offers wait for all destination scopes to start normally and cannot cross into reentrant structured scopes. Parallel/iterative boundaries allow outgoing crossings only to nodes directly in the immediately enclosing scope. Direct offers do not change output-pin aggregation or introduce interleaving. See Crossing structured boundaries for execution ownership and activation rules.

public sealed record Item(string Name, decimal Price, int Quantity);
public sealed record Order(string Id, IReadOnlyList<Item> Items);

Clone this wiki locally