Page 20 - MSDN Magazine, May 2017
P. 20

Figure 2, which parses a string containing a class definition, gets its corresponding syntax node and calls a new static method that generates a View Model from the syntax node.
The GenerateViewModel method is going to be defined in a static class called ViewModelGeneration, so add a new file called ViewModelGeneration.cs to the project. The method looks for a class definition in the input syntax node (for demonstration purposes, the first instance of a ClassDeclarationSyntax object), then constructs a new View Model based on the class’s name and members. Figure 3 demonstrates this.
In the first part of the code in Figure 3, you can see how the View Model is first represented as a string, with string interpolation that makes it easy to specify object and member names based on the original class name. In this sample scenario, plurals are generated just by adding an “s” to the object/member name; in real-world code you should use more specific pluralization algorithms.
In the second part of Figure 3, the code invokes CSharpSyntax- Tree.ParseText to parse the source text into a SyntaxTree. GetRoot is invoked to retrieve the SyntaxNode for the new tree; with DescendantNodes().OfType<ClassDeclarationSyntax>(), the code retrieves only the syntax nodes that represent a class, selecting only the first one with FirstOrDefault. Retrieving the first class in the syntax node is enough to get the parent namespace where the new View Model class will be inserted. Obtaining a namespace is
Figure 5 Checking for Code Issues with the Diagnostic APIs
possible by casting the Parent property of a ClassDeclarationSyntax into a NamespaceDeclarationSyntax object. Because a class could be nested into another class, the code first checks for this possibility by verifying that Parent is of type NamespaceDeclarationSyntax. The final piece of the code adds the new syntax node for the View Model class to the parent namespace, returning this as a syntax node. If you now press F5, you’ll see the result of the code genera- tion in the Debug Console, as shown in Figure 4.
The generated View Model class is a SyntaxNode that the C# compiler can work with, so it can be further manipulated, ana- lyzed for diagnostic information, compiled into an assembly with the Emit APIs and utilized via Reflection.
Getting Diagnostic Information
Whether source text comes from a string, a file or user input, you can take advantage of the Diagnostic APIs to retrieve diagnostic information about code issues such as errors and warnings. Remember that the Diagnostic APIs not only allow for retrieving errors and warnings, they also allow writing analyzers and code refactorings. Continuing the previous example, it’s a good idea to check for syntactic errors in the original source text before attempting to generate a View Model class. To accomplish this, you can invoke the SyntaxNode.GetDiagnostics method, which returns an IEnumerable<Microsoft.CodeAnalysis.Diagnostic>
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using System;
namespace RoslynCore {
public static class ViewModelGeneration {
public static SyntaxNode GenerateViewModel(SyntaxNode node) {
// Find the first class in the syntax node var classNode =
node.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
if(classNode!=null) {
var codeIssues = node.GetDiagnostics(); if(!codeIssues.Any())
{
// Get the name of the model class
var modelClassName = classNode.Identifier.Text;
// The name of the ViewModel class
var viewModelClassName = $"{modelClassName}ViewModel";
// Only for demo purposes, pluralizing an object is done by // simply adding the "s" letter. Consider proper algorithms string newImplementation =
$@"public class {viewModelClassName} : INotifyPropertyChanged
{{
public event PropertyChangedEventHandler PropertyChanged; // Raise a property change notification
protected virtual void OnPropertyChanged(string propname) {{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname)); }}
private ObservableCollection<{modelClassName}> _{modelClassName}s; public ObservableCollection<{modelClassName}> {modelClassName}s {{
get {{ return _{modelClassName}s; }} set
{{
_{modelClassName}s = value;
OnPropertyChanged(nameof({modelClassName}s)); }}
}}
public {viewModelClassName}() {{
// Implement your logic to load a collection of items }}
}}
";
var newClassNode = SyntaxFactory.ParseSyntaxTree(newImplementation).GetRoot() .DescendantNodes().OfType<ClassDeclarationSyntax>() .FirstOrDefault();
// Retrieve the parent namespace declaration if(!(classNode.Parent is NamespaceDeclarationSyntax)) return null;
var parentNamespace = (NamespaceDeclarationSyntax)classNode.Parent; // Add the new class to the namespace
var newParentNamespace =
parentNamespace.AddMembers(newClassNode).NormalizeWhitespace(); return newParentNamespace;
} else {
foreach(Diagnostic codeIssue in codeIssues) {
string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}, Location: {codeIssue.Location.GetLineSpan()},
Severity: {codeIssue.Severity}";
Console.WriteLine(issue); }
return null; }
} else {
return null; }
} }
}
16 msdn magazine
.NET Core


































































































   18   19   20   21   22