Click here to Skip to main content
Click here to Skip to main content

Refly, makes the CodeDom'er life easier

By , 1 Mar 2004
 

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):

// creating the Refly.Demo namespace
CodeNamespace demo = new CodeNamespace("Refly.Demo");

// create the User class
CodeTypeDeclaration user = new CodeTypeDeclaration("User");
user.IsClass = true;
demo.Types.Add(user);

// add name field
CodeMemberField name = new CodeMemberField(typeof(string),"name");
user.Members.Add(name);

// add constructor
CodeConstructor cstr = new CodeConstructor();
user.Members.Add(cstr);
CodeParameterDeclarationExpression pname = 
    new CodeParameterDeclarationExpression(typeof(string),"name");
cstr.Parameters.Add(pname);

// this.name = name;
CodeFieldReferenceExpression thisName = new CodeFieldReferenceExpression(
    new CodeThisReferenceExpression(),
    "name");
CodeAssignStatement assign = new CodeAssignStatement(
    thisName,
    pname
    );
cstr.Statements.Add(assign);

// add property
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:

// creating the Refly namespace
NamespaceDeclaration demo= new NamespaceDeclaration("Refly.Demo");

// create the user class
ClassDeclaration user = demo.AddClass("User");

// add name field
FieldDeclaration name = user.AddField(typeof(string), "name");

// add constructor
ConstructorDeclaration cstr = user.AddConstructor();
// add name parameter
ParameterDeclaration pname = 
    cstr.Signature.AddParam(typeof(string), "name",true);

// this.name = name;
cstr.Body.AddAssign( 
    Expr.This.Field(name),
    Expr.Arg(pname)
    ); 

// add property
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:

// changin output to VB
gen.Provider=CodeGenerator.VbProvider;

Adding members

Back to our user class, you can add fields, methods, etc... by using the proper methods of ClassDeclaration:

// add field name
FieldDeclaration name = user.AddField(typeof(string),name);
// add Check
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.

// adding age to the check 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);

// set base class as CollectionBase
col.Parent = new TypeTypeDeclaration(typeof(DictionaryBase));

// default constructor
col.AddConstructor();

// add indexer
IndexerDeclaration index = col.AddIndexer(
    this.ValueType
    );
ParameterDeclaration pindex = index.Signature.AddParam(KeyType,"key",false);
// get body
index.Get.Return(
    (Expr.This.Prop("Dictionary").Item(Expr.Arg(pindex)).Cast(this.ValueType)
    )
    );

// set body
index.Set.AddAssign(
    Expr.This.Prop("Dictionary").Item(Expr.Arg(pindex)),
    Expr.Value
    );
            
// add method
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)
    );

// contains method
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)
    );

// remove method
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

  1. 2-03-2004, First release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Jonathan de Halleux
Engineer
United States United States
Member
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 1member[CC]22 Oct '12 - 22:42 
Questionhow to user AttributeDeclaration [modified]memberwuhuacong22 Mar '12 - 5:12 
GeneralExtension Methodsmemberslifer1226 Jan '11 - 17:36 
QuestionLatest version?memberMichael P Butler26 Aug '06 - 9:49 
http://www.dotnetwiki.org/ can't be accessed at the moment, is there anywhere else I can get the latest version?
 

 

AnswerThis version seems newermemberMichael P Butler28 Aug '06 - 8:29 
GeneralRe: This version seems newermemberZac Greve6 Mar '12 - 5:54 
GeneralHelpmemberHayseeds12 Jul '06 - 13:49 
QuestionEnums and NamespacesmemberManuel Salvatore18 May '06 - 6:56 
GeneralBase args for constructormemberdj peregrine19 Feb '06 - 12:27 
GeneralRe: Base args for constructormemberjscote3 Mar '06 - 2:36 
GeneralRe: Base args for constructormemberdj peregrine3 Mar '06 - 14:53 
GeneralProject cannot be builtmemberdirc15 Nov '05 - 4:31 
AnswerRe: Project cannot be builtmemberDemeter3 Apr '08 - 0:41 
QuestionHow to add a region block?memberwolfsecond24 Jun '05 - 20:31 
AnswerRe: How to add a region block?memberJonathan de Halleux24 Jun '05 - 20:33 
GeneralCode quotationssussKamil Skalski19 Dec '04 - 23:37 
GeneralRe: Code quotationsmemberJonathan de Halleux24 Jun '05 - 20:36 
GeneralGreat but still struggling to make use of reflymembervbnetuk6 Dec '04 - 2:32 
GeneralHelp on Object Arraysmemberterence wallace8 Oct '04 - 18:51 
GeneralRe: Help on Object ArraysmemberJonathan de Halleux24 Oct '04 - 9:54 
GeneralRe: Help on Object ArraysmemberWallace.TL25 Oct '04 - 7:32 
GeneralRe: Help on Object ArraysmemberJonathan de Halleux25 Oct '04 - 19:09 
GeneralDownloading latest version.memberMotoMan04516 Aug '04 - 19:00 
GeneralRe: Downloading latest version.memberJonathan de Halleux16 Aug '04 - 21:24 
GeneralRe: Downloading latest version.membervbnetuk3 Dec '04 - 4:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 2 Mar 2004
Article Copyright 2004 by Jonathan de Halleux
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid