4

I am attempting to dynamically compile code and execute it in runtime. So I followed http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn as a guide.

The code given in the example works prefectly. However, if I use Console.ReadKey() it gives me error CS0117: 'Console' does not contain a definition for 'ReadKey'. I read somewhere that this is because dotnet core does not support ReadKey ('Console' does not contain a definition for 'ReadKey' in asp.net 5 console App), but I am currently using "Microsoft.NETCore.App" and it Console.ReadKey() works perfectly if I use it explicitly in code instead of while using Roslyn.

  • Is this an issue with Roslyn or am I doing something wrong?
  • Are "Microsoft.NETCore.App" and dotnet core the same thing? I suspect I might be using something else as my target (which allows me to use ReadKey) and dotnet core with Roslyn
  • Is it possible to change Roslyn's target to something else?

Thanks in advance.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;

namespace DemoCompiler
{
    class Program
    {
        public static void roslynCompile()
        {
            string code = @"
    using System;
    using System.Text;

    namespace RoslynCompileSample
    {
        public class Writer
        {
            public void Write(string message)
            {
                Console.WriteLine(message);
                Console.ReadKey();
            }
        }
    }";
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);

            string assemblyName = Path.GetRandomFileName();
            MetadataReference[] references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location)
            };

            CSharpCompilation compilation = CSharpCompilation.Create(
                assemblyName,
                syntaxTrees: new[] { syntaxTree },
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                EmitResult result = compilation.Emit(ms);

                if (!result.Success)
                {
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic => 
                        diagnostic.IsWarningAsError || 
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures)
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                }
                else
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
                    var type= assembly.GetType("RoslynCompileSample.Writer");
                    var instance = assembly.CreateInstance("RoslynCompileSample.Writer");
                    var meth = type.GetMember("Write").First() as MethodInfo;
                    meth.Invoke(instance, new [] {assemblyName});
                }
            }

        }
    }
}

Edit : I tried to reference System.Console.dll but I got conflict between that and System.Private.CoreLib. How do I resolve it?

Community
  • 1
  • 1
Grimson
  • 540
  • 1
  • 5
  • 21
  • I'm no expert on .NET core, but given how much is it designed around components, it's a good guess that `typeof(object)` and `typeof(Enumerable)` aren't enough references to bring in all the references you have in your main project (outside of .NET Core, it is - `Console` is in `mscorlib` in .NET proper, but not in .NET Core). In particular, you're missing `System.Console.dll`, which I'd guess is the culprit. You need to reference a whole bunch of libraries to get what's in a default C# project :) – Luaan Mar 13 '17 at 13:54
  • After reading your comment I tried referencing `System.Console.dll`. However I got a conflict between two assemblies. I guess its a known issue as written here https://github.com/dotnet/roslyn/issues/16211 – Grimson Mar 13 '17 at 17:38
  • I wouldn't consider this a "known issue" in that you should necessarily wait for us to fix it: the "typeof(object).Assembly.Location" pattern that people often do to initialize compilations is just fundamentally not going to work in .NET Core worlds. It's a shortcut that just happens to work in some .NET Framework scenarios. If you're in .NET Core you'll need to collect stuff other ways. – Jason Malinowski Mar 13 '17 at 21:52
  • @JasonMalinowski What would be a recommended, simple "other way"? – svick Mar 13 '17 at 22:11

2 Answers2

0

One approach would be to add <PreserveCompilationContext>true</PreserveCompilationContext>* to your project file and then use Microsoft.Extensions.DependencyModel.DependencyContext to get all reference assemblies for current project:

var references = DependencyContext.Default.CompileLibraries
    .SelectMany(l => l.ResolveReferencePaths())
    .Select(l => MetadataReference.CreateFromFile(l));

This seems to avoid the errors you get, but causes warnings like:

warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'System.Console' matches identity 'System.Runtime, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy

As far as I can tell, this should not be an issue.


* This assume you're using csproj/VS2017. If you're still on project.json/VS2015, the same can be done using "buildOptions": { "preserveCompilationContext": true }.

svick
  • 236,525
  • 50
  • 385
  • 514
  • What about VSCode? – Grimson Mar 13 '17 at 23:27
  • @Nick_McCoy I believe VS Code just uses whatever version of the .Net Core SDK you have installed. So if you have 1.0.0 (not preview), then the csproj instructions should work for you. – svick Mar 14 '17 at 00:36
0

Found a similar issue at https://github.com/dotnet/roslyn/issues/13267 with the solution.

I have got around this (as far as I can tell) by:

Copying System.Runtime.dll and System.Runtime.Extensions.dll from: C:\Users\<user>\.nuget\packages\System.Runtime\4.1.0\ref\netstandard1.5 and putting them in a References folder in my project. So then instead of referencing mscorlib.dll and System.Private.CoreLib.dll I reference these

I tried this and found referencing only System.Runtime.dll is sufficient (at least in my case). Just to be clear, instead of MetadataReference.CreateFromFile(typeof(Object).GetTypeInfo().Assembly.Location), I now use MetadataReference.CreateFromFile("System.Runtime.dll") which refers to the DLL present in the working directory directly.

The only downside being I'll have to distribute the dll along with my project but that is okay considering I can now use Roslyn properly on .net core instead of changing the base framework to full .net

Community
  • 1
  • 1
Grimson
  • 540
  • 1
  • 5
  • 21