Using C# 6 features with CodeDomProvider (Roslyn)

Update: March 2018

Word of caution, NuGet version 1.0.6 ... 1.0.8 will not copy the /roslyn folder to the build output directory on non-web projects. Best stick with 1.0.5 https://github.com/aspnet/RoslynCodeDomProvider/issues/38

Run-time compilation using C#6 features requires a new compiler, as @thomas-levesque mentioned. This compiler can be installed by using the nuget package Microsoft.CodeDom.Providers.DotNetCompilerPlatform.

For desktop applications, there's a problem. The ASP.NET team, in their infinite wisdom have hard-coded the path to the compiler as <runtime-directory>\bin\roslyn\csc.exe See discussion at https://github.com/dotnet/roslyn/issues/9483

If your desktop application is compiled to \myapp\app.exe, the roslyn compiler will be located at \myapp\roslyn\csc.exe, BUT THE CSharpCodeProvider WILL RESOLVE csc.exe as \myapp\bin\roslyn\csc.exe

As far as I can tell, you have two options

  1. Create a post-build and/or installation routine that will move the \roslyn subdirectory to \bin\roslyn.
  2. Fix the runtime code through reflection black magic.

Here is #2, by exposing the CSharpCodeProvider as a property in a utility class.

using System.Reflection;
using Microsoft.CodeDom.Providers.DotNetCompilerPlatform;

static Lazy<CSharpCodeProvider> CodeProvider { get; } = new Lazy<CSharpCodeProvider>(() => {
    var csc = new CSharpCodeProvider();
    var settings = csc
        .GetType()
        .GetField("_compilerSettings", BindingFlags.Instance | BindingFlags.NonPublic)
        .GetValue(csc);

    var path = settings
        .GetType()
        .GetField("_compilerFullPath", BindingFlags.Instance | BindingFlags.NonPublic);

    path.SetValue(settings, ((string)path.GetValue(settings)).Replace(@"bin\roslyn\", @"roslyn\"));

    return csc;
});

The built-in CodeDOM provider doesn't support C# 6. Use this one instead:

https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/

It's based on Roslyn and supports the C# 6 features.

Just change this line:

CodeDomProvider objCodeCompiler = CodeDomProvider.CreateProvider( "CSharp" );

to this:

CodeDomProvider objCodeCompiler = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();

Ran into this issue recently. For context, I was trying to run an MSTest project against a library project using System.CodeDom, but it always gave a compiler that implemented C# 5 whether or not I had Microsoft.Net.Compilers or Microsoft.CodeDom.Providers.DotNetCompilerPlatform packages referenced by the project under test.

My fix for this was:

  • Use package Microsoft.CodeDom.Providers.DotNetCompilerPlatform
  • Set package PrivateAssets to contentfiles;analyzers
  • Pass provider options with CompilerDirectoryPath set to the copied directory

The default value for PrivateAssets is contentfiles;analyzers;build, so getting referencing projects to also copy the folder requires removing build from the setting.

Example code:

var compiler = CodeDomProvider.CreateProvider("cs", new Dictionary<string, string> {
    { "CompilerDirectoryPath", Path.Combine(Environment.CurrentDirectory, "roslyn") }
});

Getting this to work with Microsoft.Net.Compilers would be slightly more tedious as no copy is made, but the end step of pointing CompilerDirectoryPath at the package's tools folder is the same.