Be constructive, comment your votes...
Introduction
With CodeDom, .NET has given us a flexible tool for generating source code in a variety of languages. Unfortunately, CodeDom has a major shortcoming, it is quite "verbose": Generating a little piece of code usually means writing dozens of CodeDom instructions.
This article presents Refly, a smart wrapper that should dramatically simplify code generation with CodeDom. The major features of Refly are:
- Smart naming of the members, fields and properties,
- Easy creation of namespace, classes, ...
- Output to separate file for each class, with directories mapping the namespaces
- Intuitive syntax for building expressions. For example,
this.data.Close();
is generated by:
Expr.This.Field("data").Method("Close").Invoke();
- Static helpers for common statements:
if (value==null)
throw new ArgumentNullExpression("value");
is generated by the static method:
Stm.ThrowIfNull(value);
- Documentation writing helpers
- Built-in templates: strongly typed collection, dictionary, data reader
- ...
We will start with a comparative example on CodeDom and Refly and then, give a how-to on using Refly.
Comparative example: User class generation
Suppose that we want to create the following simple User
class:
namespace Refly.Demo
{
public class User
{
private string name;
public User(string name)
{
this.name = name;
}
public String Name
{
get
{
return this.name;
}
}
}
}
CodeDom solution
Here comes a part of the CodeDom code to generate the above class (skip this if you know CodeDom):
CodeNamespace demo = new CodeNamespace("Refly.Demo");
CodeTypeDeclaration user = new CodeTypeDeclaration("User");
user.IsClass = true;
demo.Types.Add(user);
CodeMemberField name = new CodeMemberField(typeof(string),"name");
user.Members.Add(name);
CodeConstructor cstr = new CodeConstructor();
user.Members.Add(cstr);
CodeParameterDeclarationExpression pname =
new CodeParameterDeclarationExpression(typeof(string),"name");
cstr.Parameters.Add(pname);
CodeFieldReferenceExpression thisName = new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
"name");
CodeAssignStatement assign = new CodeAssignStatement(
thisName,
pname
);
cstr.Statements.Add(assign);
CodeMemberProperty p = new CodeMemberProperty();
p.Type=name.Type;
p.Name = "Name";
p.HasGet = true;
p.GetStatements.Add(
new CodeMethodReturnStatement(thisName)
);
Refly solution
Here comes the Refly version of the above. Although you do not know yet Refly, you will see that it is similar in many ways to CodeDom:
NamespaceDeclaration demo= new NamespaceDeclaration("Refly.Demo");
ClassDeclaration user = demo.AddClass("User");
FieldDeclaration name = user.AddField(typeof(string), "name");
ConstructorDeclaration cstr = user.AddConstructor();
ParameterDeclaration pname =
cstr.Signature.AddParam(typeof(string), "name",true);
cstr.Body.AddAssign(
Expr.This.Field(name),
Expr.Arg(pname)
);
user.AddProperty(name, true, false, false);
Comparison
At first look, the two versions do not look very different, let me point out some interesting differences:
- Add instances into their parents: in CodeDom, it is your job to add the new instance into their parents. In Refly, this is done by the framework. Therefore, you avoid forgetting to add the type into the parent.
- Expression and Statement construction:
this.name = name;
. Expression and statement building has been highly simplified in Refly as you can see it in this 1 line example. Imagine the economy of work when you need to generate methods with dozens of lines.
- Smart helpers: wrapping a field by a property is a usual task. This is done automatically by Refly.
- Output: (not shown in the example), Refly will create the directory structure mapping the namespaces and output a file for each
class
/enum
.
This concludes the introductory comparison example. The full source of the example is available in the demo.
Refly Architecture
The Refly framework is organized in the following components:
Refly.CodeDom
, root namespace for the CodeDom generator,
Refly.CodeDom.Collections
, useful strongly typed collections,
Refly.CodeDom.Expressions
, expression wrappers,
Refly.CodeDom.Statements
, statement wrappers,
Refly.CodeDom.Doc
, documentation writing helpers,
Refly.Templates
, some generator classes,
Refly.Xsd
, XSD refactoring tools
Refly acts as a wrapper around CodeDom: each class of the CodeDom namespace has its Refly counter part. When Refly is asked to generate the code, it creates the CodeDom code and lets CodeDom generate the code.
Usually, importing the Refly.CodeDom
is sufficient to have all the functionalities of Refly.
Generating code
This section gives step-by-step instructions to get you code generator running.
Creating a namespace
First of all, we need a namespace, that is an instance of NamespaceDeclaration
:
NamespaceDeclaration ns = new NamespaceDeclaration("MyNs");
Classes and enums can be added to this namespace. Sub-namespaces can also be added to namespaces.
Adding a class
The NamespaceDeclaration
class provides a method AddClass
to create a new class:
ClassDeclaration user = ns.AddClass("user");
You do not need to worry about the naming case, Refly will "recase" the name you provided. Now that we have an empty type, we can test the generator.
Generating code
Refly provides the CodeGenerator
class that takes care of creating the directories, calling CodeDom generator for each type in the namespace:
CodeGenerator gen = new CodeGenerator();
gen.GenerateCode("outputPath",ns);
By default, CodeGenerator
will create C# code, but of course, you can change the output language by providing another provider:
gen.Provider=CodeGenerator.VbProvider;
Adding members
Back to our user class, you can add fields, methods, etc... by using the proper methods of ClassDeclaration
:
FieldDeclaration name = user.AddField(typeof(string),name);
MethodDeclaration check = user.AddMethod("Check");
...
The signature of members (when it applies) is controlled by a MethodSignature
through the Signature
property. This object lets you add parameters and set the return type of the method.
ParameterDeclaration age = check.Signature.AddParam(typeof(int),"age");
Adding comments
Each declaration type has a Documentation instance that can be used to create comments:
age.Doc.Summary.AddText("user age");
Building Expressions
The class Expr
contains a number of static helper classes that will generate all the expression objects for you. Here are some code samples, with their Refly counter part:
this.name
-> Expr.This.Field(name)
; where
Expr.This
returns the this
instance
.Field
retrieves the field from an expression
this.Check(10)
-> Expr.This.Method(check).Invoke(Expr.Prim(10))
; where
.Method
retrieves the method,
.Invoke
invokes the method with the arguments,
Expr.Prim
creates an expression from a primitive value (int
, string
, double
, etc...)
- ...
The Expr
contains other methods to map typeof
, new
, cast
, ...
Building Statements
As for expressions, the Stm
class contains a lot of static helper classes to simplify your work. For example, simple statements such assign
, return
:
left = right;
-> Stm.Assign( left, right);
return value;
-> Stm.Return( value );
if (value==null) throw new ArgumentNullException("value");
-> Stm.ThrowIfNull(value);
Throw new Exception();
-> Stm.Throw(typeof(Exception));
More complex statements like if
, for
, try
/catch
/finally
are also created with Stm
. Refly also emulates foreach
, which is not supported by CodeDom, by expanding the foreach
as the C# compiler would be (using an enumerator, etc...).
More built-in examples
The Refly.Templates
contains various generators for strongly-typed collection, dictionary and IDataReader
wrapper. They are good starting point to get a grip on Refly. As a final example, this method generates a strongly typed dictionary (KeyType
is the key type, ValueType
is the value type):
NamespaceDeclaration ns = ...;
ClassDeclaration col = ns.AddClass(this.Name);
col.Parent = new TypeTypeDeclaration(typeof(DictionaryBase));
col.AddConstructor();
IndexerDeclaration index = col.AddIndexer(
this.ValueType
);
ParameterDeclaration pindex = index.Signature.AddParam(KeyType,"key",false);
index.Get.Return(
(Expr.This.Prop("Dictionary").Item(Expr.Arg(pindex)).Cast(this.ValueType)
)
);
index.Set.AddAssign(
Expr.This.Prop("Dictionary").Item(Expr.Arg(pindex)),
Expr.Value
);
MethodDeclaration add = col.AddMethod("Add");
ParameterDeclaration pKey = add.Signature.AddParam(this.KeyType,"key",true);
ParameterDeclaration pValue = add.Signature.AddParam(this.ValueType,"value",true);
add.Body.Add(
Expr.This.Prop("Dictionary").Method("Add").Invoke(pKey,pValue)
);
MethodDeclaration contains = col.AddMethod("Contains");
contains.Signature.ReturnType = new TypeTypeDeclaration(typeof(bool));
ParameterDeclaration pKey = contains.Signature.AddParam(this.KeyType,"key",true);
contains.Body.Return(
Expr.This.Prop("Dictionary").Method("Contains").Invoke(pKey)
);
MethodDeclaration remove = col.AddMethod("Remove");
ParameterDeclaration pKey = remove.Signature.AddParam(this.KeyType,"key",true);
remove.Body.Add(
Expr.This.Prop("Dictionary").Method("Remove").Invoke(pKey)
);
The last bonus
Refly contains a last bonus, XsdTidy, that refactors the output of the Xsd.exe tool to nicer and easier to use classes.
History
- 2-03-2004, First release.