Skip to content

Instantly share code, notes, and snippets.

@GeorgDangl
Last active June 10, 2021 08:13
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save GeorgDangl/4a9982a3b520f056a9e890635b3695e0 to your computer and use it in GitHub Desktop.
Save GeorgDangl/4a9982a3b520f056a9e890635b3695e0 to your computer and use it in GitHub Desktop.
namespace InMemoreCompilation
{
public static class CodeGenerator
{
public static string GenerateCalculator()
{
var calculator = @"namespace Calculator
{
public class Calculator
{
public int AddIntegers(int x, int y)
{
return x + y;
}
}
}";
return calculator;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using InMemoreCompilation;
using Xunit;
namespace InMemoryCompilation.Tests
{
public class CodeGeneratorTests
{
public CodeGeneratorTests()
{
GenerateCode();
CreateCompilation();
CompileAndLoadAssembly();
}
private string _generatedCode;
private CSharpCompilation _compilation;
private Assembly _generatedAssembly;
[Theory]
[InlineData(1,1,2)]
[InlineData(5,5,10)]
[InlineData(9,9,18)]
public void GenerateCompileAndTestCode(int x, int y, int expectedResult)
{
var calculatedResult = CallCalculatorMethod(x, y);
Assert.Equal(expectedResult, calculatedResult);
}
private void GenerateCode()
{
_generatedCode = CodeGenerator.GenerateCalculator();
}
private int CallCalculatorMethod(int x, int y)
{
var calculatorType = _generatedAssembly.GetType("Calculator.Calculator");
var calculatorInstance = Activator.CreateInstance(calculatorType);
var calculateMethod = calculatorType.GetTypeInfo().GetDeclaredMethod("AddIntegers");
var calculationResult = calculateMethod.Invoke(calculatorInstance, new object[] { x, y });
Assert.IsType(typeof(int), calculationResult);
return (int)calculationResult;
}
private void CreateCompilation()
{
var syntaxTree = CSharpSyntaxTree.ParseText(_generatedCode);
string assemblyName = Guid.NewGuid().ToString();
var references = GetAssemblyReferences();
var compilation = CSharpCompilation.Create(
assemblyName,
new[] { syntaxTree },
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
_compilation = compilation;
}
private static IEnumerable<MetadataReference> GetAssemblyReferences()
{
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location)
// A bit hacky, if you need it
//MetadataReference.CreateFromFile(Path.Combine(typeof(object).GetTypeInfo().Assembly.Location, "..", "mscorlib.dll")),
};
return references;
}
private void CompileAndLoadAssembly()
{
using (var ms = new MemoryStream())
{
var result = _compilation.Emit(ms);
ThrowExceptionIfCompilationFailure(result);
ms.Seek(0, SeekOrigin.Begin);
var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);
#if NET46
// Different in full .Net framework
var assembly = Assembly.Load(ms.ToArray());
#endif
_generatedAssembly = assembly;
}
}
private void ThrowExceptionIfCompilationFailure(EmitResult result)
{
if (!result.Success)
{
var compilationErrors = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error)
.ToList();
if (compilationErrors.Any())
{
var firstError = compilationErrors.First();
var errorNumber = firstError.Id;
var errorDescription = firstError.GetMessage();
var firstErrorMessage = $"{errorNumber}: {errorDescription};";
throw new Exception($"Compilation failed, first error is: {firstErrorMessage}");
}
}
}
}
}
{
"version": "1.0.0",
"dependencies": {
"InMemoreCompilation": "*",
"xunit": "2.2.0",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.CodeAnalysis.CSharp": "2.0.0"
},
"testRunner": "xunit",
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment