I have been experimenting with the C# dynamic compilation as described in Laurent's excellent blog: https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (Merci Laurent!!)
I copied and pasted the code into a single file and all in the Main method to understand the control flow better.
I have however been unable to work out why the unloading of the DLL consistently fails (i.e. the WeakReference is still live). Laurent's code (as published on GitHub) does unload the DLL while my copy-pasted monolithic code does not.
Could someone help me spot where I have gone wrong?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace CoreCompile
{
public class CompilerTest
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
string sourcePath = args.Length > 0 ? args[0] : #"D:\DynamicRun\Sources\DynamicProgram.cs";
string sourceCode = File.ReadAllText(sourcePath);
string assemblyPath = Path.ChangeExtension(Path.GetFileNameWithoutExtension(sourcePath), "DLL");
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
};
Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList()
.ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
var csCompilation = CSharpCompilation.Create(assemblyPath,
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
WeakReference assemblyLoadContextWeakRef = null;
using (var peStream = new MemoryStream())
{
var result = csCompilation.Emit(peStream);
if (result.Success)
{
Console.WriteLine("Compilation done without any error.");
peStream.Seek(0, SeekOrigin.Begin);
var compiledAssembly = peStream.ToArray();
string[] arguments = new[] { "France" };
using (var asm = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
var assembly = assemblyLoadContext.LoadFromStream(asm);
var entry = assembly.EntryPoint;
_ = entry != null && entry.GetParameters().Length > 0
? entry.Invoke(null, new object[] { arguments })
: entry.Invoke(null, null);
assemblyLoadContext.Unload();
assemblyLoadContextWeakRef = new WeakReference(assemblyLoadContext);
} // using
}
else
{
Console.WriteLine("Compilation done with error.");
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
foreach (var diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
} // using
if (assemblyLoadContextWeakRef != null)
{
for (var i = 0; i < 8 && assemblyLoadContextWeakRef.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine(assemblyLoadContextWeakRef.IsAlive ? "Unloading failed!" : "Unloading success!");
}
} // Main
} // class
internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext()
: base(true)
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
return null;
}
}
} // namespace
Forcing objects to be garbage collected is something of a dark art. You need to ensure that the garbage collector will not be able to locate any variables from your Main method, otherwise the objects will be kept alive.
For example, a debug build and a release build will behave differently, as release builds will throw away variables as soon as they are no longer required.
From your example, your local variable assemblyLoadContext will still be in scope, particularly in a debug build. As you might place a break point at the end of the method in order to examine any local variable.
Perhaps the simplest thing you could do is move most of your code to a separate method. Once that method returns, all local variables should be out of scope and undetectable by the garbage collector.
Related
I'm writing a project that dynamically compiles and executes c# code. The problem is that sometimes I want the code to call another DLL (for the sake of this sample I called it "ANOTHER.DLL"). It works fine in .Net 4.5, but fails in .Net Core and I can't figure out why. Any help is appreciated!
Code compiles successfully, but gives an error when the method is executed. Error is:
FileNotFoundException: Could not load file or assembly 'ANOTHER,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system
cannot find the file specified.
The ANOTHER.dll is located in the same /bin/debug folder, and is definitely accessible (code compiles!)
I noticed I can fix the issue by adding reference to ANOTHER.DLL to the project, but it defeats the purpose of dynamic compilation.
I tried this in .Net Core 2.0 - 3.1
ANOTHER.DLL is .Net Standard 2.0 (but same result with .Net Standard 2.1, or .Net Framework).
Also tried various versions of Microsoft.CodeAnalysis package, all giving me same error.
var eval = new Evaluator();
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List < MetadataReference > references = new List < MetadataReference > ();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
string ReferenceList = "";
ReferenceList += "netstandard.dll\n";
ReferenceList += "System.Runtime.dll\n";
ReferenceList += "ANOTHER.dll\n";
string[] assemblies = ReferenceList.Split('\n');
foreach(string a in assemblies) {
if (File.Exists(Path.Combine(assemblyPath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, a.Trim())));
}
else if (File.Exists(a.Trim())) {
string currDirectory = Directory.GetCurrentDirectory();
references.Add(MetadataReference.CreateFromFile(Path.Combine(currDirectory, a.Trim())));
}
else {
string exepath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (File.Exists(Path.Combine(exepath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(exepath, a.Trim())));
}
}
}
CSharpCompilation compilation = CSharpCompilation.Create("assembly", syntaxTrees: new[] {
syntaxTree
},
references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
Assembly assembly;
using(var ms = new MemoryStream()) {
EmitResult result = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(ms.ToArray());
}
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x =>x.Name == "Hello");
var info = all.FirstOrDefault(x =>x.DeclaringType == type) ? ?all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] {}); // this is where the error occurs
Solution is based on my gist
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
var tree = SyntaxFactory.ParseSyntaxTree(code);
string fileName = "mylib.dll";
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "netstandard.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
var anotherDLLReference = MetadataReference.CreateFromFile(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
references.Add(anotherDLLReference);
var compilation = CSharpCompilation.Create(fileName)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(references)
.AddSyntaxTrees(tree);
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
EmitResult compilationResult = compilation.Emit(path);
if (compilationResult.Success)
{
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x => x.Name == "Hello");
var info = all.FirstOrDefault(x => x.DeclaringType == type) ?? all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] { });
}
}
}
}
To be fair, I have 0 idea how it works, since I am not familiar with working with assemblies on this level, but somehow I managed to get rid of exception.
Firstly, I checked AssemblyLoadContext.Default in the debugger. I noticed that reference to "ANOTHER.dll" is missing (although we previously added it)
Then I added AssemblyLoadContext.Default.LoadFromAssemblyPath(#"path to my ANOTHER.dll");. And when I checked it again - ANOTHER.dll was there.
Finally, we can see our hello world message
So the code I added is basically one line
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
var type = assembly.GetType("RoslynCompileSample.Test");
This works with both ANOTHER.dll targeting Standard 2.0 and .NET Core 3.1
Would be nice if someone smart actually told how it works.
I'm a beginner of roslyn, so I tried to start learning it by making a very simple console application, which is introduced in the famous tutorial site. (https://riptutorial.com/roslyn/example/16545/introspective-analysis-of-an-analyzer-in-csharp), and it didn't work well.
The Cosole Application I made is of .NET Framework (target Framework version is 4.7.2), and not of .NET Core nor .NET standard.
I added the NuGet package Microsoft.CodeAnalysis, and Microsoft.CodeAnalysis.Workspaces.MSBuild, then wrote a simple code as I show below.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;
namespace SimpleRoslynConsole
{
class Program
{
static void Main(string[] args)
{
// Declaring a variable with the current project file path.
// *** You have to change this path to fit your development environment.
const string projectPath =
#"C:\Users\[MyName]\Source\Repos\RoslynTrialConsole01\RoslynTrialConsole01.csproj";
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
// [**1]Getting the compilation.
var compilation = project.GetCompilationAsync().Result;
// [**2]As this is a simple single file program, the first syntax tree will be the current file.
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
if (syntaxTree != null)
{
var rootSyntaxNode = syntaxTree.GetRootAsync().Result;
var firstLocalVariablesDeclaration = rootSyntaxNode.DescendantNodesAndSelf()
.OfType<LocalDeclarationStatementSyntax>().First();
var firstVariable = firstLocalVariablesDeclaration.Declaration.Variables.First();
var variableInitializer = firstVariable.Initializer.Value.GetFirstToken().ValueText;
Console.WriteLine(variableInitializer);
}
else
{
Console.WriteLine("Could not get SyntaxTrees from this projects.");
}
Console.WriteLine("Hit any key.");
Console.ReadKey();
}
}
}
My problem is that, SyntaxTrees property of Compilation object returns null in [**2]mark. Naturally, following FirstOrDefault method returns null.
I've tried several other code. I found I could get SyntaxTree from CSharp code text, by using CSharpSyntaxTree.ParseText method. But I couldn't get any from source code, by the sequence of
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
var compilation = project.GetCompilationAsync().Result;
What I'd like to know is if I miss something to get Syntax information from source code by using above process.
I'll appreciate someone give me a good advice.
I think the issue is that .net framework projects have their source files paths within their .csproj. And opening project works right away.
For .net core project you have no such information and, maybe, this is why Workspace instance doesn't know what to load and so loads nothing.
At least specifying .cs files as added documents does the trick. Try to apply this:
static class ProjectExtensions
{
public static Project AddDocuments(this Project project, IEnumerable<string> files)
{
foreach (string file in files)
{
project = project.AddDocument(file, File.ReadAllText(file)).Project;
}
return project;
}
private static IEnumerable<string> GetAllSourceFiles(string directoryPath)
{
var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
return res;
}
public static Project WithAllSourceFiles(this Project project)
{
string projectDirectory = Directory.GetParent(project.FilePath).FullName;
var files = GetAllSourceFiles(projectDirectory);
var newProject = project.AddDocuments(files);
return newProject;
}
}
Method WithAllsourceFiles will return you the project, compilation of which will in its turn have all syntax trees you would expect of it, as you would have in Visual Studio
MsBuildWorkspace won't work correctly unless you have all the same redirects in your app's app.config file that msbuild.exe.config has in it. Without the redirects, it's probably failing to load the msbuild libraries. You need to find the msbuild.exe.config file that is on your system and copy the <assemblyBinding> elements related to Microsoft.Build assemblies into your app.config. Make sure you place them under the correct elements configuration/runtime.
I searched various sample programs on the net and found the most reliable and safest method. The solution is to create a static method which returns SyntaxTrees in designated File as follow.
private static Compilation CreateTestCompilation()
{
var found = false;
var di = new DirectoryInfo(Environment.CurrentDirectory);
var fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
while ((fi == null) || (di.Parent == null))
{
di = new DirectoryInfo(di.Parent.FullName);
fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
if (fi != null)
{
found = true;
break;
}
}
if (!found)
{
return null;
}
var targetPath = di.FullName + #"\Program.cs";
var targetText = File.ReadAllText(targetPath);
var targetTree =
CSharpSyntaxTree.ParseText(targetText)
.WithFilePath(targetPath);
var target2Path = di.FullName + #"\TypeInferenceRewriter.cs";
var target2Text = File.ReadAllText(target2Path);
var target2Tree =
CSharpSyntaxTree.ParseText(target2Text)
.WithFilePath(target2Path);
SyntaxTree[] sourceTrees = { programTree, target2Tree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(
OutputKind.ConsoleApplication));
}
And the caller program will be like this.
static void Main(string[] args)
{
var test = CreateTestCompilation();
if (test == null)
{
return;
}
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
Console.WriteLine(souceTree.ToFullString());
}
}
Of course, many improvements are needed to put it to practical use.
So i'm having this problem.
I'm trying to compile code in memory and adding namespace references by searching the syntax tree so i do not add them manually. Trying to simulate how Visual Studio maybe does it.
I'm a bit over my head in the compilation department. Even if i add a metadata reference to System while reading the syntax tree it does not find System.Console.
The key is that i want it to include the assemblies by itself, i do not want to add a "MetadataReference.CreateFromFile(....,"System.Console").
I explained the code below so that is clear what is happening.
class App
{
static void Main(string[] args)
{
//creating the syntax tree for the program
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
namespace ns{
using System;
public class App{
public static void Main(string[] args){
Console.Write(""dada"");
}
}
}");
//creating options that tell the compiler to output a console application
var options = new CSharpCompilationOptions(
OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true);
//creating the compilation
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), options: options);
//adding the syntax tree
compilation = compilation.AddSyntaxTrees(syntaxTree);
//getting the local path of the assemblies
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().ChildNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
//for each using directive add a metadatareference to it
foreach (var u in usings)
{
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, u.Name.ToString() + ".dll")));
}
//add the reference list to the compilation
compilation=compilation.AddReferences(references);
//compile
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}, {2}", diagnostic.Id, diagnostic.GetMessage(), diagnostic.Location);
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);
assembly.EntryPoint.Invoke(null, new object[] { new string[] { "arg1", "arg2", "etc" } });
}
}
}
}
In the .net core System.Console lives in the System.Console.dll. So you need to add reference on it
You need to add reference on the System.Runtime.dll to correctly resolve the predefined types: object, bool and so on
SyntaxNode.ChildNodes() returns only child, that means it doesn't return the descendents nodes, so if you want to get all UsingDirectiveSyntax you should change your logic. As one of way just use SyntaxNode.DescendantNodes()
After applying all suggestions you just get something likes this (The parts that didn't change will skipped):
...
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Console.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().DescendantNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
...
I'm trying to play around with the end user preview of roslyn and would like to execute a simple script. What I would like to do is something like:
static void Main(string[] args)
{
// Is this even valid?
var myScript = "int x = 5; int y = 6; x + y;";
// What should I do here?
var compiledScript = Something.Compile(myScript);
var result = compiledScript.Execute(myScript);
Console.WriteLine(result);
}
Can someone point to some resources and/or tell me which nuget packages to install to make this happen. I've installed the Microsoft.CodeAnalysis, but can't figure out if it doable with just that, I feel like I'm missing something.
The scripting APIs which would allow you to do this very easily were (temporarily) removed in the latest preview. You can still compile a script, emit and load the assembly and invoke its entry point by doing something along the lines of
public static class Program
{
public static void Main(string[] args)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
var defaultReferences = new[] { "mscorlib.dll", "System.dll", "System.Core.dll" };
var script = #"using System;
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine(""Hello {0}"", args[0]);
}
}";
// Parse the script to a SyntaxTree
var syntaxTree = CSharpSyntaxTree.ParseText(script);
// Compile the SyntaxTree to a CSharpCompilation
var compilation = CSharpCompilation.Create("Script",
new[] { syntaxTree },
defaultReferences.Select(x => new MetadataFileReference(Path.Combine(assemblyPath, x))),
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
using (var outputStream = new MemoryStream())
using (var pdbStream = new MemoryStream())
{
// Emit assembly to streams.
var result = compilation.Emit(outputStream, pdbStream: pdbStream);
if (!result.Success)
{
return;
}
// Load the emitted assembly.
var assembly = Assembly.Load(outputStream.ToArray(), pdbStream.ToArray());
// Invoke the entry point.
assembly.EntryPoint.Invoke(null, new object[] { new[] { "Tomas" } });
}
}
}
It will output Hello Tomas in the console :)
It appears that in the April 2014 release, scripting has been temporarily removed:
What happened to the REPL and hosting scripting APIs?
The team is reviewing the designs of these components that you saw in
previous CTPs, before re-introducing the components again. Currently
the team is working on completing the language semantics of
interactive/script code.
I have this console type thing that accepts a line of C# code, wraps it up in some surrounding code and compiles it into an assembly. Then, I invoke the method from that assembly, output the result and that's it.
The problem is, the assembly needs to have a name so I can set it as a friend assembly so it could access the non-public classes. I named it "console".
Everything worked as expected but the problem is that I can't run a second script after one has finished because the file named "console" already exists in the directory and can't be overwritten.
I've tried Disposing of everything that has a Dispose method. I've tried manually deleting the file with File.Delete. Nothing helped.
So here's the code I'm using. I hope someone can help me.
CSharpCodeProvider provider = new CSharpCodeProvider();
var param = new CompilerParameters();
param.GenerateInMemory = false;
param.GenerateExecutable = false;
param.OutputAssembly = "console";
param.ReferencedAssemblies.Add("System.dll");
param.ReferencedAssemblies.Add("System.Core.dll");
param.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
var results = provider.CompileAssemblyFromSource(param,
#"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace FireflyGL
{
class DebugConsole
{
static void Run(StringWriter writer)
{
var stdOut = Console.Out;
Console.SetOut(writer);
" + input.Text + #"
Console.SetOut(stdOut);
}
}
}");
if (results.Errors.HasErrors)
{
for (int i = 0; i < results.Errors.Count; ++i)
{
PushText(results.Errors[i].ErrorText);
}
}
else
{
try
{
var writter = new StringWriter();
results.CompiledAssembly.GetType("FireflyGL.DebugConsole").GetMethod("Run", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { writter });
PushText(writter.ToString());
history.Add(input.Text);
currentHistory = history.Count;
input.Text = "";
writter.Dispose();
}
catch (Exception)
{
}
}
provider.Dispose();
File.Delete(results.CompiledAssembly.Location);
You have to unload the assembly to get rid of all the references. Unfortunately, you can't do that. You can however unload an AppDomain and provided you reference your assembly in that AppDomain, it will be unloaded as well.
If you don't care about creating a memory leak, you could also just create unique names for your assembly (console1, console2 etc.)...