【发布时间】:2016-07-28 20:58:04
【问题描述】:
我想在另一个非脚本 Roslyn 编译中将脚本作为动态程序集重用,但我终其一生都无法弄清楚如何实现这一目标。
例如,假设我以正常方式创建脚本,然后将脚本作为程序集发送到字节流,使用类似:
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = script.GetCompilation().WithOptions(compilationOptions);
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(ms.ToArray());
}
现在,假设我想将该程序集作为参考提供给另一个非脚本编译。我不能只使用assembly,因为没有MetadataReference.CreateFrom...() 方法支持传递实际的Assembly 实例。作为一个动态程序集,它没有位置,所以我不能使用MetadataReference.CreateFromFile()。
过去我曾成功使用MetadataReference.CreateFromStream() 处理此类事情,但当程序集表示脚本提交时,这似乎不起作用(我不知道为什么)。编译继续进行,但一旦您尝试使用提交中的类型,您就会收到如下错误:
System.InvalidCastException: [A]Foo cannot be cast to [B]Foo. Type A originates from 'R*19cecf20-a48e-4a31-9b65-4c0163eba857#1-0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' in a byte array. Type B originates from 'R*19cecf20-a48e-4a31-9b65-4c0163eba857#1-0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' in a byte array.
我猜这与提交程序集在评估与加载为字节数组时处于不同的上下文中有关。对于在以后的非脚本编译中使用脚本提交中定义的对象和方法的最佳方式,我很乐意提供任何见解或指导。
7/29 更新
我能够获得演示问题的最小重现。可以在https://github.com/daveaglick/ScriptingAssemblyReuse找到。
在生成重现时,很明显这个问题的一个重要组成部分是脚本将其中一个类的Type 传递给调用代码,然后调用代码使用该Type 来实例化一个对象的实例,然后将该实例传递到引用脚本程序集的编译中。当从宿主应用程序创建的类型的实例转换为引用编译中的类型时,会发生不匹配。当我重新阅读时,这听起来很混乱,所以希望下面的代码能让它更清晰。
以下是触发此问题的所有代码:
namespace ScriptingAssemblyReuse
{
public class Globals
{
public IFactory Factory { get; set; }
}
public interface IFactory
{
object Get();
}
public class Factory<T> : IFactory where T : new()
{
public object Get() => new T();
}
public class Program
{
public static void Main(string[] args)
{
new Program().Run();
}
private Assembly _scriptAssembly;
public void Run()
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
// Create the script
Script<object> script = CSharpScript.Create(@"
public class Foo { }
Factory = new ScriptingAssemblyReuse.Factory<Foo>();
", ScriptOptions.Default.WithReferences(MetadataReference.CreateFromFile(typeof(IFactory).Assembly.Location)), typeof(Globals));
// Create a compilation and get the dynamic assembly
CSharpCompilationOptions scriptCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
Compilation scriptCompilation = script.GetCompilation().WithOptions(scriptCompilationOptions);
byte[] scriptAssemblyBytes;
using (MemoryStream ms = new MemoryStream())
{
EmitResult result = scriptCompilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
scriptAssemblyBytes = ms.ToArray();
}
_scriptAssembly = Assembly.Load(scriptAssemblyBytes);
// Evaluate the script
Globals globals = new Globals();
script.RunAsync(globals).Wait();
// Create the consuming compilation
string assemblyName = Path.GetRandomFileName();
CSharpParseOptions parseOptions = new CSharpParseOptions();
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
public class Bar
{
public void Baz(object obj)
{
Script.Foo foo = (Script.Foo)obj; // This is the line that triggers the exception
}
}", parseOptions, assemblyName);
CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
string assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] {syntaxTree},
new[]
{
MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")),
MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")),
MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll"))
}, compilationOptions);
using (MemoryStream ms = new MemoryStream(scriptAssemblyBytes))
{
compilation = compilation.AddReferences(MetadataReference.CreateFromStream(ms));
}
// Get the consuming assembly
Assembly assembly;
using (MemoryStream ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
byte[] assemblyBytes = ms.ToArray();
assembly = Assembly.Load(assemblyBytes);
}
// Call the consuming assembly
Type barType = assembly.GetExportedTypes().First(t => t.Name.StartsWith("Bar", StringComparison.Ordinal));
MethodInfo bazMethod = barType.GetMethod("Baz");
object bar = Activator.CreateInstance(barType);
object obj = globals.Factory.Get();
bazMethod.Invoke(bar, new []{ obj }); // The exception bubbles up and gets thrown here
}
private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
if (_scriptAssembly != null && args.Name == _scriptAssembly.FullName)
{
// Return the dynamically compiled script assembly if given it's name
return _scriptAssembly;
}
return null;
}
}
}
【问题讨论】:
-
你能分享整个堆栈跟踪吗?以及实际导致它的代码?您描述的场景有效。另外-您如何引用脚本中的类型?他们有一个非法的标识符 (
#)。 -
@EliArbel 该应用程序非常复杂,因此我试图将其提炼成更简单的复制品。我很难获得上面提到的确切 InvalidCastException ,而是在我尝试在消费编译中使用提交中的类型时获得 TypeLoadException 。我觉得这很有趣,并且可能相关。我到目前为止的复制品在这里:github.com/daveaglick/ScriptingAssemblyReuse
-
@EliArbel 我已在链接的 GitHub 存储库及以上代码中添加了示例代码
-
您有什么理由不只是使用编译到编译的参考吗?
-
@JasonMalinowski 这实际上是我一直在做的,而且效果很好。我试图首先切换到脚本引擎,以利用它自动将内联类、方法等从脚本方法中提升到容器类中/与容器类一起使用的方式。由于这个问题,我从来没有让脚本引擎工作,但我能够通过使用
Script源类型编译然后使用 walker 访问顶部来获得我所追求的编译到编译参考树中的节点并提取可推广的节点。
标签: c# .net .net-assembly roslyn