Roslyn C# 的CSharpCompilation - 编译动态内容

3

Roslyn编译器和动态属性-->我试图编译一个只在运行时已知的属性。
在Visual Studio中编译代码时,代码可以正常工作。但是在使用Roslyn编译时,动态属性会变成“未知”。

在这个示例单元测试中,我有一个从DynamicObject继承的MyObject对象。属性由简单的KeyValue字典提供。

当以硬编码方式使用'MyObject'时,我可以调用属性Hello。实际上,在编译时可以使用任何属性。不存在的属性将在运行时出错。(期望的行为)

当在传递给Roslyn编译器的代码中使用'MyObject'时,无法使用动态对象上的任何属性。在这里,属性“Hello”会给我错误:

CS1061 - 'MyObject' does not contain a definition for 'Hello' and no accessible extension method 'Hello' accepting a first argument of type 'MyObject' could be found (are you missing a using directive or an assembly reference?)

我错过了什么?

示例单元测试:

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Testing {
    [TestClass]
    public class FullExampleTest {
        [TestMethod]
        public void HardCoded() {
            var map = new Dictionary<string, string>() {
                { "Hello","Foo"},
                { "World","Bar"}
            };
            dynamic src = new MyObject(map);
            Console.WriteLine(src.Hello);

            Assert.AreEqual("Foo Bar", $"{src.Hello} {src.World}");
        }

        [TestMethod]
        public void CompileAtRuntime() {
            string code = @"
                using System;
                using System.Collections.Generic;
                using System.Diagnostics.Contracts;
                using System.Dynamic;
                using System.IO;
                using System.Linq;
                using System.Reflection;
                using Testing;

                namespace MyNamespace{{
                    public class MyClass{{
                        public static void MyMethod(MyObject src){{
                            Console.WriteLine(src.Hello);
                        }}
                    }}
                }}
           ";

            var ns = Assembly.Load("netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51");

            MetadataReference[] references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(ns.Location), //netstandard
                MetadataReference.CreateFromFile(typeof(Object).Assembly.Location), //mscorlib
                MetadataReference.CreateFromFile(typeof(DynamicObject).Assembly.Location), //System.Core
                MetadataReference.CreateFromFile(typeof(RuntimeBinderException).Assembly.Location),//Microsoft.CSharp
                MetadataReference.CreateFromFile(typeof(Action).Assembly.Location), //System.Runtime
                MetadataReference.CreateFromFile(typeof(FullExampleTest).Assembly.Location) // this assembly
            };


            var comp = CSharpCompilation.Create(
                assemblyName: Path.GetRandomFileName(),
                syntaxTrees: new[] { CSharpSyntaxTree.ParseText(code) },
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );

            using (var ms = new MemoryStream()) {
                var result = comp.Emit(ms);
                if (!result.Success) {
                    var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures) {
                        Console.WriteLine($"{diagnostic.Id} - {diagnostic.GetMessage()}");
                    }
                }
                Assert.IsTrue(result.Success, "Compilation failure..");
            }
        }
    }

    public class MyObject : DynamicObject {
        private IDictionary<string, string> _Map;

        public MyObject(IDictionary<string, string> map) {
            _Map = map;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            Contract.Assert(binder != null);

            var ret = _Map.TryGetValue(binder.Name, out string value);
            result = value;
            return ret;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder,     object[] args, out object result) {
            Contract.Assert(binder != null);

            var ret = _Map.TryGetValue(binder.Name, out string value);
            result = value;
            return ret;
        }

    }
}

也许这对你有帮助https://dev59.com/7GAh5IYBdhLWcg3wDfuy - So_oP
@So_oP,我已经尝试了那篇帖子中提到的所有方法... 你能具体说明我错过了什么吗? - JDC
1个回答

3
你动态编译的代码和静态编译的代码不一样。在你的动态编译代码中,你已经明确声明了srcdynamic。你的“硬编码”示例尝试将其作为MyObject处理,这会导致相同的问题。如果你的硬编码测试看起来像这样,也会出现相同的问题:
    var src = new MyObject(map);
    Console.WriteLine(src.Hello);

你可以通过将 src 强制转换为 dynamic 类型来解决此问题:

public static void MyMethod(MyObject src){
    Console.WriteLine(((dynamic)src).Hello);
}

或者一开始就将其声明为动态的:

public static void MyMethod(dynamic src){
    Console.WriteLine(src.Hello);
}

1
好的,非常感谢! #撞头墙 这是一个典型的看错地方的例子。 - JDC
1
很高兴能够帮忙。有时候我们只需要一个朋友以全新的视角来看待代码。 :-) - StriplingWarrior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接