Click or drag to resize

Code Generation and Saving

Examples of generating C# code objects and saving them as text.

Generate a new Solution, Project, and CodeUnit (with code in it) and save them:
C#
// Create a new solution and project
const string path = "Generated\\";
Solution solution = new Solution(path + "Generated");  // file extension will default to '.sln' if omitted
Project project = solution.CreateProject(path + "Generated");  // file extension will default to '.csproj' if omitted
project.OutputType = Project.OutputTypes.Exe;  // Output type will default to Library if not set

// In order to have external symbolic references resolved, assembly references must be added, then loaded.
// However, this is not necessary in this case, since we are only saving the generated code, and UnresolvedRefs
// will save just fine using their text names.
//project.AddDefaultAssemblyReferences();  // Add commonly used assembly references
//project.LoadReferencedAssembliesAndTypes();

// Add a file to the project, and put some code in it
CodeUnit codeUnit = project.CreateCodeUnit(path + "Program");  // file extension will default to '.cs' if omitted
codeUnit.Add(
    new UsingDirective(project.ParseName("System")),
    new UsingDirective(project.ParseName("System.Collections.Generic")),
    new UsingDirective(project.ParseName("System.Linq")),
    new UsingDirective(project.ParseName("System.Text")),
    new NamespaceDecl(project.ParseName("Generated"))
        {
            Body = { new ClassDecl("Program")
                         {
                             Body = { new MethodDecl("Main", typeof(void), Modifiers.Static, new ParameterDecl("args", typeof(string[])))
                                          {
                                              Body = { new Comment("Add code here") }
                                          }}
                         }}
        });

// External references will be unresolved, unless we add and load assembly references as shown up above
Log.WriteLine("UnresolvedRefs = " + codeUnit.CalculateMetrics().UnresolvedReferences);

// If code is simple enough, object initializers can be used as shown above, but when there are symbolic references,
// objects need to be assigned to local variables so that references can be easily generated.  This is shown in the
// next example, which generates a code fragment.  Also, note the use of 'project.ParseName()' in the UsingDirectives
// (which aren't necessary in this case, just shown as examples) - this is a helper method that parses a string into
// an expression of NamespaceRef and/or TypeRefs and Dot operators if present.  If the namespaces or types can't be
// found in scope (in the referenced assemblies), then UnresolvedRefs will be used.  Manually created code is usually
// created with resolved references, and so needs no resolving, but UnresolvedRefs can also be used if desired.

// Manually created objects will be default formatted - there's no reason to worry about braces, parentheses, commas,
// semi-colons, newlines, whitespace, or anything else that is purely syntax-related and not semantic.  However, the
// default formatting can be overridden using various properties, such as NewLines, HasBraces, HasParens.  You may
// use '.IsFirstOnLine = true' to force an object to start on a new line (or '.NewLines = 1'), or '.IsSingleLine = true'
// to force an Expression or Statement to be formatted on a single line.

// For more examples of manual code creation, see ManualTests.GenerateFullTest() in the Nova.Test solution, which
// generates a replica of the related FullTest.cs file, which includes most C# language features.

// Save the entire solution, including all projects and files
solution.SaveAll();

// You may also save a single project and all of its files using 'project.SaveAll()', or you may save just an individual
// solution, project, or file by calling '.Save()' on it.  Also, '.SaveAs(string)' methods are provided to save under
// a different file name.
Generate a code fragment, and convert it to a string:
C#
// Any code object can be created without a parent, and with whatever children are desired.  For example, you might
// create a ClassDecl or a MethodDecl, or you can create a BlockDecl (a block of statements delimited by braces, as
// shown below), or you can create an individual Statement or Expression of any kind.
var sum = new LocalDecl("sum", typeof(int), 0);
var flag = new LocalDecl("flag", typeof(bool), false) { EOLComment = "some flag" };
var i = new LocalDecl("i", typeof(int), 1);
var @for = new For(i, new LessThan(i, 10), new Increment(i),
new Block(new AddAssign(sum, i), new Assignment(flag, true))) { Comment = "calculate a sum" };
var block = new BlockDecl(sum, flag, @for);

// When a named code object (such as 'i' above), is used where an Expression is expected, there are implicit conversion
// operators which create SymbolicRefs (in this case, a LocalRef), avoiding the need to call '.CreateRef()' each time.
// Likewise, Types (such as 'typeof(int)') used for Expressions are implicitly converted to TypeRefs (avoiding the need to
// use 'new TypeRef(typeof(int))', and literals (such as '0' or 'false') are implicitly converted to Literals (avoiding
// the need to use 'new Literal(0)' or 'new Literal(false)'.

// Only Solution, Project, and CodeUnit objects implement the IFile interface and are mapped to files.  For all other code
// objects, there is no '.Save()' method.  However, you may convert any code object to a string using 'AsString()'
// (the 'ToString()' method generates only a 'description' instead of the full object, because otherwise it would cause
// performance issues when working in the debugger if all objects rendered all of their child objects).
Log.WriteLine(block.AsString());