Using the Roslyn API with Visual Studio 2015, can I convert an object instance to source code? Can I create an extension method like ".ToSourceCode()" as shown below?
class Foo { }
class Program
{
static string classSourceCode = "class Foo { }";
static void Main()
{
var instance = new Foo();
var instanceSourceCode = instance.GetType().ToSourceCode();
System.Diagnostics.Debug.Assert(instanceSourceCode == classSourceCode);
}
}
No. However, ILSpy can.
Based on the comments on the question and what I understand about Roslyn, decompilation is not supported. However, thanks to #Bradley's ILSpy tip, there is a solution:
Download the ILSpy binaries from http://ilspy.net/
Reference the following assemblies: ICSharpCode.Decompiler.dll, ILSpy.exe, Mono.Cecil.dll, ILSpy.BamlDecompiler.Plugin.dll
Implement the ".ToSourceCode()" extension method as shown below:
using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy;
using Mono.Cecil;
class Foo { }
class Program
{
static string classSourceCode = "using System; internal class Foo { } ";
static void Main()
{
var instance = new Foo();
var instanceSourceCode = instance.GetType().ToSourceCode();
System.Diagnostics.Debug.Assert(instanceSourceCode == classSourceCode);
}
}
static class TypeExtensions
{
public static string ToSourceCode(this Type source)
{
var assembly = AssemblyDefinition.ReadAssembly(Assembly.GetExecutingAssembly().Location);
var type = assembly.MainModule.Types.FirstOrDefault(t => t.FullName == source.FullName);
if (type == null) return string.Empty;
var plainTextOutput = new PlainTextOutput();
var decompiler = new CSharpLanguage();
decompiler.DecompileType(type, plainTextOutput, new DecompilationOptions());
return Regex.Replace(Regex.Replace(plainTextOutput.ToString(), #"\n|\r", " "), #"\s+", " ");
}
}
Related
I am creating a Visual Studio Extension, in which on executing certain command, a .cs file should be generated with a class implementing an Interface and all the members should be automatically implemented as well.
Example:
Suppose I have following interface,
interface IFile
{
void ReadFile();
void WriteFile(string text);
}
And I want to generate following code:
using System;
namespace TestProject
{
public class FileInfo : IFile
{
public void ReadFile()
{
}
public void WriteFile(string text)
{
}
}
}
But All I am able to generate is following:
using System;
namespace TestProject
{
public class FileInfo : IFile
{
}
}
Code Written to generate above code is as below:
AdhocWorkspace adhocWorkspace = new AdhocWorkspace();
var syntaxGenerator = SyntaxGenerator.GetGenerator(adhocWorkspace, LanguageNames.CSharp);
var usingSystem = syntaxGenerator.NamespaceImportDeclaration("System");
var interfaceNode = new List<SyntaxNode>() { SyntaxFactory.ParseTypeName("IFile") };
var classDeclaration = syntaxGenerator.ClassDeclaration("FileInfo",
null,
Accessibility.Public,
DeclarationModifiers.None,
null,
interfaceNode,
null
);
var namespaceDeclaration = syntaxGenerator.NamespaceDeclaration("TestProject", classDeclaration);
var generatedCode = syntaxGenerator.CompilationUnit(usingSystem, namespaceDeclaration)
.NormalizeWhitespace().ToFullString();
How can I implement all the members using CodeGenerator?
Or Is there a command which I can use like the command to Format the Document as below
dte.ExecuteCommand("Edit.FormatDocument", string.Empty);
You can use SyntaxGenerator.MethodDeclaration Method, which is like:
var cloneMethoDeclaration = generator.MethodDeclaration("ReadFile",
null,
null,
null,
Accessibility.Public,
DeclarationModifiers.None,
null);
And I also found a sample from github, hope it can help you.
I am trying to write a visualizer that maps expressions in an expression tree to Roslyn syntax nodes, in order to generate code for the expression tree. Part of the syntax tree generation is a call to the AdhocWorkspace constructor.
When I run the visualizer using the VisualizerDevelopmentHost, everything works just fine:
using Microsoft.VisualStudio.DebuggerVisualizers;
using System;
using System.Linq.Expressions;
namespace _testVisualizer {
class Program {
[STAThread]
static void Main(string[] args) {
Expression<Func<bool>> expr = () => true;
var data = new TestVisualizerData(expr);
var visualizerHost = new VisualizerDevelopmentHost(data, typeof(TestVisualizer));
visualizerHost.ShowVisualizer();
Console.ReadKey(true);
}
}
}
But when I try to use the visualizer through the Visual Studio UI (by hovering over expr, clicking on the magnifying glass icon and choosing my visualizer), I get the following message:
Unable to perform function evaluation on the process being debugged.
Additional information
The function evaluation requires all threads to run.
I've identified the following as triggering the error:
workspace = new AdhocWorkspace();
which assigns to the workspace field on my Mapper class (source).
Why does calling the AdhocWorkspace constructor trigger this warning? How can I work around this?
This is an MCVE that demonstrates the issue:
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
[assembly: DebuggerVisualizer(typeof(_testVisualizer.TestVisualizer), typeof(_testVisualizer.TestVisualizerDataObjectSource), Target = typeof(System.Linq.Expressions.Expression), Description = "Test Visualizer")]
namespace _testVisualizer {
public class TestVisualizer : DialogDebuggerVisualizer {
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) {
var data = (TestVisualizerData)objectProvider.GetObject();
var txt = new TextBlock();
txt.SetBinding(TextBlock.TextProperty, "Status");
var window = new Window {
DataContext = data,
Content = txt
};
window.ShowDialog();
}
}
[Serializable]
public class TestVisualizerData {
public TestVisualizerData() { }
public TestVisualizerData(System.Linq.Expressions.Expression expr) {
var workspace = new AdhocWorkspace();
Status = "Success";
}
public string Status { get; set; }
}
public class TestVisualizerDataObjectSource : VisualizerObjectSource {
public override void GetData(object target, Stream outgoingData) {
var expr = (System.Linq.Expressions.Expression)target;
var data = new TestVisualizerData(expr);
base.GetData(data, outgoingData);
}
}
}
Perhaps the AdhocWorkspace alternate constructor could be leveraged to use the SyntaxNode API in a single thread.
I've filed an issue.
When I'm getting IInstanceReferenceExpression operation for type Instance() => this; instance kind is Explicit, but I expect that kind would be This. Am I missing something or this is a bug in Roslyn?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Semantics;
using System;
using System.Linq;
internal static class so39495857
{
private static void Main()
{
var tree = CSharpSyntaxTree.ParseText(#"
class c
{
c Instance() => this;
static void Main() {}
}
");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create(null, new[] { tree }, new[] { mscorlib });
var model = compilation.GetSemanticModel(tree);
var node = tree.GetRoot().DescendantNodes().OfType<ArrowExpressionClauseSyntax>().First();
var operation = model.GetOperation(node);
var block = (IBlockStatement)operation;
var #return = (IReturnStatement)block.Statements.First();
var #this = (IInstanceReferenceExpression)#return.ReturnedValue;
Console.WriteLine(#this.InstanceReferenceKind);
}
}
Project on github - https://github.com/isanych/so-39495857
That's what Explicit means.
ThisClass is only for VB's MyClass keyword.
I have a promblem with run-time compiled classes. I have something like this 2 classes:
first class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Program.Bullet {
public class Class1{
private int i;
public Class1(int j){
i=j;
}
}
}
and second class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Program.Bullet;
namespace Program.Object {
public class Class2{
public Class2(){
Class1 c1 = new Class1(5);
}
}
}
This two classes I would like to compile in run-time and use them in my project. So I have function to compile it (XmlNode has data about fullPath etc):
private ModuleBuilder moduleBuilder;
private List<string> isCompiled;
private void Compile(XmlNode compileingClasses) {
foreach (XmlNode compileingClass in compileingClasses) {
string fullPath = compileingClass.Attributes["path"].InnerText;
string type = compileingClass.Attributes["name"].InnerText; ;
if (!isCompiled.Contains(type)) {
isCompiled.Add(type);
var syntaxTree = SyntaxTree.ParseFile("../../File1/File2/" + fullPath);
var comp = Compilation.Create("Test.dll"
, syntaxTrees: new[] { syntaxTree }
, references: metadataRef
, options: comilationOption
);
// Runtime compilation and check errors
var result = comp.Emit(moduleBuilder);
if (!result.Success) {
foreach (var d in result.Diagnostics) {
Console.WriteLine(d);
}
throw new XmlLoadException("Class not found " + fullPath);
}
}
}
}
Is it possible to get the reference on Class1 to Class2?
Edit: Better question
Is it possible to create MetadataReference on compiled Class1?
Something like:
string fullName = bullet.Attributes["fullName"].InnerText;
var o = moduleBuilder.GetType(fullName);
metadataRef.Add(new MetadataFileReference(o.Assembly.Location));
This throw NotSupportedException
You're trying to reference the assembly which is currently being built and I don't think Roslyn can do that.
What you can do instead is to create a single Compilation from all your classes (probably having a separate SyntaxTree for each class). If you do that, you won't need any references.
im having a Problem creating a Class at runtime. Everytime i debug the code below i get the following error message at var cls = results.CompiledAssembly.GetType("test.DummyHelloWorldHandler");
Could not load file or assembly 'file:///C:\Users\MyName\AppData\Local\Temp\1ivc3qic.dll' or one of its dependencies. Das System kann die angegebene Datei nicht finden.
the Name of the *.dll file differs everytime i debug the programm
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace DynamicNS
{
class Program
{
static void Main(string[] args)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.ReferencedAssemblies.Add("System.Collections.dll");
CompilerResults results = provider.CompileAssemblyFromSource(parameters, GetCode());
var cls = results.CompiledAssembly.GetType("test.DummyHelloWorldHandler");
var method = cls.GetMethod("Received", BindingFlags.Static | BindingFlags.Public);
object[] parms = { "Hallo Welt" };
method.Invoke(null, parms);
Console.ReadLine();
}
static string[] GetCode()
{
return new string[]
{
#"using System.Collections;
namespace test
{
public class DummyHelloWorldHandler
{
protected internal Queue _queue;
public void Received(string message)
{
lock (_queue)
{
_queue.Enqueue(message);
}
Console.WriteLine('Enqueued');
}
public DummyHelloWorldHandler()
{
_queue = new Queue();
}
}
}"
};
}
}
}
The code returned by GetCode does not compile because of the wrong quotes.
You can check that by iterating over the Errors property of your CompilerResults.
You have to remove this line:
parameters.ReferencedAssemblies.Add("System.Collections.dll");
and change the GetCode() method like this:
private static string[] GetCode()
{
return new string[]
{
#"using System;
using System.Collections;
namespace test
{
public class DummyHelloWorldHandler
{
protected internal Queue _queue;
public void Received(string message)
{
lock (_queue)
{
_queue.Enqueue(message);
}
Console.WriteLine(""Enqueued"");
}
public DummyHelloWorldHandler()
{
_queue = new Queue();
}
}
}"
};
}
#"using System.Collection
Should probably be
#"using System.Collections;
Also: Console.WirteLine()? Hmmm. Perhaps you should paste that entire GetCode() string into a test program, make it compile, and then paste it back into your original project.
After your edits, this still won't compile:
Console.WriteLine('Enqueued');
It's got single quotes instead of double quotes around the string.
Are you sure your code could actually be compiled? You seem to be missing a semicolon after your using statement.
Check the Errors property of your results, it contains the errors that were encountered while compiling your source.