Literal<T> and Reference Types


[Edit – I had to cut the pictures, they got broken by software update issues.]

With two forum posts in two days vaguely related to Literal<T>, suddenly the Literal<T> problem seems interesting again.

Shortly prior to the release of Visual Studio 2010 and .net 4.0, there was a list of breaking changes posted on the endpoint blog, with one section that I was mostly too lazy to think about at the time.

Reference Types in Literal <T> Expressions

Description: Literal expressions are represented in WF using the Literal<T> activity.  In Beta2 any type could be used with this expression.  In RC It is invalid to initialize Literal<T> with an instance of a reference type.  String is the only exception to this rule.  The motivation for the change was that users erroneously thought that creating a Literal<T> with a new reference type created the new reference type for each instance of the workflow.  Disallowing “Literal” reference types eliminates this confusion.  In Beta2 the following workflow definition would run without error.  In RC it will receive a validation error:

‘Literal<List<String>>’: Literal only supports value types and the immutable type System.String.  The type System.Collections.Generic.List`1[System.String] cannot be used as a literal.

[code snippet]

Customer Impact: Customers who have used reference types in Literal<T> expressions will need to use a different kind of expression to introduce the reference type into the workflow.  For example VisualBasicValue<T> or LambdaValue<T>.

Still, this supposedly minor change causes some significant headaches. Additionally, the only solution suggested in the post is “just use VisualBasicValue<T> to initialize the expression instead of Literal<T>” and this is a bit one-dimensional as a solution – other solutions may apply as well. 

Anyway, as in the breaking changes doc, let’s re-think about when Literal<T> where T : class is no longer allowed… firstly it is no longer allowed for most custom types. Now that sounds a little worrying.

An example illegal type:

 

    public class Distance

    {

        public int Number;

        public string Units;

 

        public override string ToString()

        {

            return string.Format(“{0}{1}”, Number, Units);

        }

    }

 

And a workflow program in code. Where it seems like a fine idea to take in a Literal<Distance> distance, and do some stuff with it (increment, and print).

    Literal<Distance> d = new Literal<Distance>(new Distance

    {

        Number = 3,

        Units = “cm”

    });

    Variable<Distance> x;

    var workflow1 = new Sequence

    {

        Variables =

        {

            (x = new Variable<Distance>{ Name = “x”, Default = d } )

        },

        Activities =

        {

            new Assign<int>

            {

                To = new VisualBasicReference<int>(“x.Number”),

                Value = new VisualBasicValue<int>(“x.Number + 1”)

            },

            new WriteLine

            {

                Text = new VisualBasicValue<string>(“x.ToString()”)

            }

        }

    };

 

    WorkflowInvoker.Invoke(workflow1);

    WorkflowInvoker.Invoke(workflow1);

 

You can try to run this code, but because of the changes mentioned above, this sample no longer runs successfully.

 

But if it still could run, what would the output be? Well, we can try to see what happens when we replace the Literal<Distance> with Literal<int>, and the x.Number with x, and it will print, innocuously “4[newline]4[newline]”.

 

But wait! Replacing ‘x.Number’ in the Assign statement with ‘x’ totally changes the semantics of the workflow. Because, now, with a reference type, we are now holding the exact same instance of the object in each invocation of the workflow. (Creating Literal<T>(x) does not clone x.)

 

“What? You can invoke the same exact workflow instance more than once?”

 

It may be surprising to many people, as most of the samples do not do this, but yes. Actually, workflow runtime allows you to be even more aggressive in your reuse of workflow instances. You can invoke exactly the same workflow objet instance or activity object instance more than once in parallel.

 

This can happen without you noticing when you use a ParallelForEach activity. In which case it is just the ‘everyday’ workflow meaning of parallelism. But the same is true for the ‘everyday’ multithreaded sense of the word parallel too.

 

OK (deep breath).

 

Now none of that may matter to you.  You need not care if in your scenario you never invoke the same workflow object more than once. You also would not appear to need to care if in your scenario you would never even think of mutating a Distance.

 

There is also a class of people there who will claim not to care – just as long as their workflow actually runs successfully most of the time, even if it is actually incorrect or the subject of race conditions.  (Just like those many C or Fortran programmers who did not care that their program exceeds array bounds without error, just as long as the program runs to completion and appears to produce a useful result [reference – the billion dollar mistake].)

 

But in case you’re not in that class, you might now be able to appreciate the unfriendly sounding error message

The following errors were encountered while processing the workflow tree:
‘Literal<Distance>’: Literal only supports value types and the immutable type System.String.  The type WorkflowConsoleApplication3.Distance cannot be used as a literal.

OK, so I’ll now stop defending a change I had absolutely no hand in. What about those very real usability problems this change introduces?

Yes! Literal<T> should appear less frequently in the samples, and yes! You need more good workarounds! Let’s view potential workarounds. And in the process, see a few ways to pass objects into a workflow.

But first – another question: Can’t I just initialize the Variable with the value directly instead? Instead of passing in a literal.

        Variables =

        {

            (x = new Variable<Distance>{

Name = “x”,

Default = new Distance {

    Number = 3, Units = “cm” } } )

        },

 

Reason 1: This code is, while to the naked eye apparently Literal<>-free, actually exactly the same as the above code, and creates an invisible (and unusable) Literal<Distance>.

Reason 2: Even if it were somehow literal free, you would probably be sharing object instances again, when actually you don’t want to.

 

So, other workarounds?

Option #1 – VisualBasicValue<> (As suggested in the original breaking change list.)

We could use VisualBasicValue<>(blarg) with an expression blarg that news up an object (VisualBasicValue(“new Distance { Number = 3, Units = “cm” }”).

 
But from C# code I really don’t like this solution, because

 

1) it’s a lot of typing and not much compile type checking

2) I don’t know real VB syntax

3) you are now at the mercy of the WF VB compiler’s inability to figure out which assemblies you want it to reference

 

In the context of the Workflow Designer (instead of code) it’s a little bit better as a solution because you get

 

1) no need to type VisualBasicValue<Distance> – you just type the expression in the ExpressionTextBox.
2) you get intellisense for VB

3) slightly smarter guesses at which assemblies you want it to reference (but it still needs guidance)

 

And you can do this in many places. A variable’s Default Value, or an Assign activity, or an InArgument.

Option # 2 – InvokeMethod activity

This is a possible solution for when you want to get an object by means other than calling a constructor. And it’s much easier for static methods, because then you have less arguments to pass. A  ‘StaticFactoryClass.Create()’ style method would be one of the easier places to take this approach.

 

To get the Result of invoking the method into a workflow variable, you bind the Result OutArgument of InvokeMethod to a Variable in your workflow. (In the screenshot I’m getting an error message telling me I supplied it a Variable<string>, not a variable<Object>. Stupid me. 🙂 )

 

 

 

Option # 3 – Custom CodeActivity

Sometimes you really just want to write code to get the job done, instead of trying to cram complex logic into a Workflow or (arg!) an Expression. Here’s an [illogically simple] example, which derives from CodeActivity<Distance>:

    public sealed class CreateDistance : CodeActivity<Distance>

    {

        protected override Distance Execute(CodeActivityContext context)

        {

            Distance newDist = new Distance

            {

                Number = 1,

                Units = “miles”,

            };

            return newDist;

        }

    }

 

And we can use the resulting activity in our workflow to initialize a variable, like this:

 

 

 

 

 

[*More workarounds which aren’t really workarounds, just solutions for different problems than the one above, i.e. Literal probably wasn’t the right idea in the first place:

1) InArguments for the workflow (top level), for ‘vanilla’ workflow execution scenarios
2) Receive activity, for workflow service scenarios
3) Workflow hosting Extensions

]


Comments (1)

  1. A Tippett says:

    If there's only a requirement to read property values of the objects passed into the workflow, such as evaluating properties of a "Customer" object for a business decision implemented as a workflow, a possible alternative may be to serialize the "Customer" object into XML and pass that into the WF instead – thereby allowing the workflow to indirectly query the property values of the Customer object similar to that if the object itself were supplied.

Skip to main content