考虑以下C#代码:
public static Foo CreateFoo()
{
return new Foo();
}
// ...
public static void Main()
{
IFoo foo = CreateFoo();
}
相当简单:赋值语句允许目标变量比表达式的类型更不具体。
所以我希望注释掉的线能够工作,但由于下面确定的原因它失败了,强迫任何设置TheFoo
返回具体Foo
类的实例而不仅仅是IFoo
:
public class ActivityWithOutArgument : CodeActivity<Foo>
{
protected override Foo Execute(CodeActivityContext context)
{
return new Foo();
}
}
<Activity x:Class="MyNamespace.TheActivity"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mine="clr-namespace:MyNamespace">
<Sequence>
<Sequence.Variables>
<!--
The following fails on the "mine:ActivityWithOutArgument" line, with:
Compiler error(s) encountered processing expression "TheFoo".
Option Strict On disallows implicit conversions from 'MyNamespace.IFoo' to 'MyNamespace.Foo'.
<Variable x:TypeArguments="mine:IFoo" Name="TheFoo" />
-->
<!-- This works -->
<Variable x:TypeArguments="mine:Foo" Name="TheFoo" />
</Sequence.Variables>
<mine:ActivityWithOutArgument Result="[TheFoo]" />
</Sequence>
</Activity>
当我处理返回一个特别令人讨厌的具体类的遗留代码时出现了这种情况,这在我们的测试套件中工作相当令人讨厌(例如,所有创建此bugger实例的测试在我们的构建服务器上失败但在本地传递,并解决这个问题是非常重要的)。
我们已经有一些OutArgument<Foo>
的现有活动(并且它们的所有测试都在构建服务器上失败,并且调用了一个非常讨厌的静态方法)。因此,当我的任务是创建一个内部使用的对应活动时,我想“好吧,所以让我们让新活动使用OutArgument<IFoo>
,以便我可以使用模拟测试它”。
因此调用活动内部的逻辑看起来像(使用C#作为类比):
public static void Run()
{
IFoo foo = TryExistingActivity();
if (foo == null)
{
foo = UseMyShinyNewActivity();
}
}
我想有一个解决方法,使它看起来像这样:
public static void Run()
{
IFoo foo;
Foo uglyWorkaround = TryExistingActivity();
if (uglyWorkaround == null)
{
foo = UseMyShinyNewActivity();
}
else
{
foo = uglyWorkaround;
}
}
......这可能就是我最终要做的事情,虽然我倾向于只是顺应传统的东西并使用shims, the most cheating thing I've ever seen作为魔杖使单元测试不会失败。
从根本上说,为什么我不能使用Variable<IFoo>
存储活动的结果OutArgument<Foo>
?或者这只是标准的“实施成本与收益”的事情?
这使用了上面“子活动定义”中的相同活动。
class Program
{
static void Main()
{
Variable<IFoo> theFoo = new Variable<IFoo>("TheFoo");
Sequence sequence = new Sequence
{
Variables =
{
theFoo
},
Activities =
{
new ActivityWithOutArgument
{
Result = theFoo
}
}
};
// The following fails with:
// InvalidWorkflowException was Unhandled
// The following errors were encountered while processing the workflow tree:
// 'VariableReference<Foo>': Variable 'System.Activities.Variable`1[MyNamespace.IFoo]' cannot be used
// in an expression of type 'MyNamespace.Foo', since it is of type 'MyNamespace.IFoo' which is not compatible.
WorkflowInvoker.Invoke(sequence);
}
}
由于WF的设计方式,输入和输出由泛型类表示,这些类不能是协变的或逆变的。
然而,WF没有什么根本可以防止这种情况,所以这有效:
using System;
using System.Activities;
using System.Activities.Statements;
using System.Activities.Expressions;
namespace WorkflowConsoleApplication1
{
public interface IFoo
{
void DoThing();
}
public sealed class Foo : IFoo
{
public void DoThing() => Console.WriteLine("Hello");
}
public sealed class ActivityWithOutArgument : CodeActivity<Foo>
{
protected override Foo Execute(CodeActivityContext context) => new Foo();
}
internal static class Program
{
private static void Main()
{
Variable<IFoo> theFoo = new Variable<IFoo>(nameof(theFoo));
Sequence sequence = new Sequence
{
Variables =
{
theFoo,
},
Activities =
{
new Assign<IFoo>
{
To = theFoo,
Value = new Cast<Foo, IFoo>
{
Operand = new ActivityWithOutArgument(),
},
},
new InvokeMethod
{
TargetObject = new InArgument<IFoo>(theFoo),
MethodName = nameof(IFoo.DoThing),
},
}
};
WorkflowInvoker.Invoke(sequence);
}
}
}