How to implement all members of an interface using CodeGenerator - c#

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.

Related

Is it possible to convert body of a method to string? [duplicate]

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+", " ");
}
}

"This function requires all threads to evaluate" after `new AdhocWorkspace()` within VS custom visualizer

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.

Roslyn - get grouped single line comments

I am writing a program in C# for extracting comments from code. I am using Roslyn compiler to do that. It's great, because I am just visiting the whole abstract syntax tree and fetching SingleLineComment trivia, MultiLineComment trivia and DocumentationComment trivia syntax from the file in solution. But there is a problem because programmers often write comments like that:
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
You can see that these are three single line comments, but I want them them to be fetched from code as one comment. Can I achieve that with Roslyn or maybe there is another way? Because that's frequent situation when programmers are writing multi line commments using single line comments syntax.
My code for extracting comments looks like this:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
namespace RoslynPlay
{
public class CommentStore
{
public List<Comment> Comments { get; } = new List<Comment>();
public void AddCommentTrivia(SyntaxTrivia trivia,
LocationStore commentLocationstore, string fileName)
{
if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia)
{
Comments.Add(new SingleLineComment(trivia.ToString(),
trivia.GetLocation().GetLineSpan().EndLinePosition.Line + 1, commentLocationstore)
{
FileName = fileName,
});
}
else if (trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
{
Comments.Add(new MultiLineComment(trivia.ToString(),
trivia.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
trivia.GetLocation().GetLineSpan().EndLinePosition.Line + 1, commentLocationstore)
{
FileName = fileName,
});
}
}
public void AddCommentNode(DocumentationCommentTriviaSyntax node,
LocationStore commentLocationstore, string fileName)
{
Comments.Add(new DocComment(node.ToString(),
node.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
node.GetLocation().GetLineSpan().EndLinePosition.Line,
commentLocationstore)
{
FileName = fileName,
});
}
}
}
and in main main file (Program.cs) I am launching comment extraction from code like this:
string fileContent;
SyntaxTree tree;
SyntaxNode root;
CommentsWalker commentWalker;
MethodsAndClassesWalker methodWalker;
string[] files = Directory.GetFiles(projectPath, $"*.cs", SearchOption.AllDirectories);
var commentStore = new CommentStore();
Console.WriteLine("Reading files...");
ProgressBar progressBar = new ProgressBar(files.Length);
foreach (var file in files)
{
fileContent = File.ReadAllText(file);
string filePath = new Regex($#"{projectPath}\\(.*)$").Match(file).Groups[1].ToString();
tree = CSharpSyntaxTree.ParseText(fileContent);
root = tree.GetRoot();
commentWalker = new CommentsWalker(filePath, commentStore);
commentWalker.Visit(root);
progressBar.UpdateAndDisplay();
}
and here is also the comment walker:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RoslynPlay
{
public class CommentsWalker : CSharpSyntaxWalker
{
private string _fileName;
private CommentStore _commentStore;
public CommentsWalker(string fileName,
CommentStore commentStore)
: base(SyntaxWalkerDepth.StructuredTrivia)
{
_fileName = fileName;
_commentStore = commentStore;
}
public override void VisitTrivia(SyntaxTrivia trivia)
{
if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia
|| trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
{
_commentStore.AddCommentTrivia(trivia, _commentLocationStore, _fileName);
}
base.VisitTrivia(trivia);
}
public override void VisitDocumentationCommentTrivia(DocumentationCommentTriviaSyntax node)
{
_commentStore.AddCommentNode(node, _commentLocationStore, _fileName);
base.VisitDocumentationCommentTrivia(node);
}
}
}
And the problem is because trivia.Kind() == SyntaxKind.SingleLineCommentTrivia extracts only single line of comments, but I want to extract single line comments blocks as one comment.

How do I mock a text output from reading a file in C# using the Moq Framework on Monodevelop

I've been banging my head on this all weekend. Basically I am doing a code kata for Game of Life and it involves reading in a text file. I take in that text file which contains two dimensional representation of the grid and stores all the points in a List of List's. I am trying to mock the text input obtained from the file to just be '\n' a new line so I can write unit tests checking that there is a new List being created within the List of Lists. I have created a file wrapper to handle the reading of the text file and that is what I am trying to mock. The code complies fine but the test fails with the error message "System.ArgumentException : The specified path is not of a legal form". It seems to still be expecting a file path but the mocking should change this behaviour right? Any help would be appreciated.
using System.Collections.Generic;
namespace GameOfLife
{
public class InitializeGrid
{
public InitializeGrid ()
{
}
public List<List<char>> CreateCharMatrix (string filePathName)
{
// Reads in the text file containing the grid data
FileWrapper fileWrapper = new FileWrapper ();
string inputGridTextFile = fileWrapper.ReadTextFromFile (filePathName);
// Creates character matrix and initialises the first sub List
List<List<char>> charMatrix = new List<List<char>> ();
charMatrix.Add(new List<char>());
int rowIndex = 0;
int colIndex = 0;
foreach (char cell in inputGridTextFile) {
if (cell == '\n') {
charMatrix.Add (new List<char> ());
rowIndex++;
colIndex = 0;
} else {
charMatrix [rowIndex] [colIndex] = cell;
colIndex++;
}
}
return charMatrix;
}
}
}
using NUnit.Framework;
using System;
using System.Collections.Generic;
using Moq;
namespace GameOfLife
[TestFixture()]
public class InitializeGridTest
{
[Test()]
public void CreateCharMatrix_EnsuresThatWhenEndOfLineReachedNewSubListCreated()
{
//Arrange
InitializeGrid initializeGrid = new InitializeGrid ();
List<List<char>> charMatrix;
string filePathName = " ";
Mock<IFileWrapper> mockFileWrapper = new Mock<IFileWrapper> ();
mockFileWrapper.Setup<string> (m => m.ReadTextFromFile (It.IsAny<string>())).Returns ("\n");
mockFileWrapper.Setup (m => m.ReadTextFromFile (It.IsAny<string>())).Returns ("\n");
//Act
charMatrix = initializeGrid.CreateCharMatrix (filePathName);
int countProvingAnAdditionalListHasBeenAdded = charMatrix.Count;
//Assert
Assert.AreEqual (2, countProvingAnAdditionalListHasBeenAdded);
}
}
using System;
using System.IO;
namespace GameOfLife
{
public class FileWrapper : IFileWrapper
{
public string ReadTextFromFile(string path)
{
return File.ReadAllText (path);
}
}
}
using System;
namespace GameOfLife
{
public interface IFileWrapper
{
string ReadTextFromFile(string filePathName);
}
}
Looking at your code the InitializeGrid is still using the FileWrapper class. It is not using a mocked class so the code is still trying to use the file system.
Your InitializeGrid class needs to use the IFileWrapper interface and not the FileWrapper class. I would look at passing the IFileWrapper interface into the constructor of the InitializeGrid class.
public class InitializeGrid
{
IFileWrapper fileWrapper;
public InitializeGrid (IFileWrapper fileWrapper)
{
this.fileWrapper = fileWrapper;
}
public List<List<char>> CreateCharMatrix (string filePathName)
{
string inputGridTextFile = fileWrapper.ReadTextFromFile (filePathName);
// More code here...
}
}
In your test you would construct the InitializeGrid object using the mocked IFileWrapper by passing the mockFileWrapper.Object to its constructor.
List<List<char>> charMatrix;
string filePathName = " ";
Mock<IFileWrapper> mockFileWrapper = new Mock<IFileWrapper> ();
mockFileWrapper.Setup<string> (m => m.ReadTextFromFile (It.IsAny<string>())).Returns ("\n");
mockFileWrapper.Setup (m => m.ReadTextFromFile (It.IsAny<string>())).Returns ("\n");
InitializeGrid initializeGrid = new InitializeGrid (mockFileWrapper.Object);

Automate class generation from an interface using field/property naming conventions

How would I automate the creation of a default implementation of a class from an interface using conventions. In other words, if I have an interface:
public interface ISample
{
int SampleID {get; set;}
string SampleName {get; set;}
}
Is there a snippet, T4 template, or some other means of automatically generating the class below from the interface above? As you can see, I want to put the underscore before the name of the field and then make the field the same name as the property, but lower-case the first letter:
public class Sample
{
private int _sampleID;
public int SampleID
{
get { return _sampleID;}
set { _sampleID = value; }
}
private string _sampleName;
public string SampleName
{
get { return _sampleName;}
set { _sampleName = value; }
}
}
I am not sure if T4 would be the easiest solution here in terms of readability but you can also use another code generation tool at your disposal: the CodeDom provider.
The concept is very straightforward: code consists of building blocks that you put together.
When the time is ripe, these building blocks are then parsed into the language of choice . What you end up with is a string that contains the source code of your newly created program. Afterwards you can write this to a textfile to allow for further use.
As you have noticed: there is no compile-time result, everything is runtime. If you really want compiletime then you should use T4 instead.
The code:
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;
namespace TTTTTest
{
internal class Program
{
private static void Main(string[] args)
{
new Program();
}
public Program()
{
// Create namespace
var myNs = new CodeNamespace("MyNamespace");
myNs.Imports.AddRange(new[]
{
new CodeNamespaceImport("System"),
new CodeNamespaceImport("System.Text")
});
// Create class
var myClass = new CodeTypeDeclaration("MyClass")
{
TypeAttributes = TypeAttributes.Public
};
// Add properties to class
var interfaceToUse = typeof (ISample);
foreach (var prop in interfaceToUse.GetProperties())
{
ImplementProperties(ref myClass, prop);
}
// Add class to namespace
myNs.Types.Add(myClass);
Console.WriteLine(GenerateCode(myNs));
Console.ReadKey();
}
private string GenerateCode(CodeNamespace ns)
{
var options = new CodeGeneratorOptions
{
BracingStyle = "C",
IndentString = " ",
BlankLinesBetweenMembers = false
};
var sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(ns, writer, options);
}
return sb.ToString();
}
private void ImplementProperties(ref CodeTypeDeclaration myClass, PropertyInfo property)
{
// Add private backing field
var backingField = new CodeMemberField(property.PropertyType, GetBackingFieldName(property.Name))
{
Attributes = MemberAttributes.Private
};
// Add new property
var newProperty = new CodeMemberProperty
{
Attributes = MemberAttributes.Public | MemberAttributes.Final,
Type = new CodeTypeReference(property.PropertyType),
Name = property.Name
};
// Get reference to backing field
var backingRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name);
// Add statement to getter
newProperty.GetStatements.Add(new CodeMethodReturnStatement(backingRef));
// Add statement to setter
newProperty.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name),
new CodePropertySetValueReferenceExpression()));
// Add members to class
myClass.Members.Add(backingField);
myClass.Members.Add(newProperty);
}
private string GetBackingFieldName(string name)
{
return "_" + name.Substring(0, 1).ToLower() + name.Substring(1);
}
}
internal interface ISample
{
int SampleID { get; set; }
string SampleName { get; set; }
}
}
This produces:
Magnificent, isn't it?
Sidenote: a property is given Attributes = MemberAttributes.Public | MemberAttributes.Final because omitting the MemberAttributes.Final would make it become virtual.
And last but not least: the inspiration of this awesomeness. Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications.

Categories