Click here to Skip to main content
15,886,519 members
Articles / Programming Languages / Visual Basic
Article

Transsubstantiate

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
2 Apr 2011CPOL5 min read 16.4K   96   6  
Copying base class constructors to derived classes

Introduction

In the immortal words of Tom Lehrer, "2, 4, 6, 8, time to transsubstantiate!" This article describes my technique for avoiding writing a lot of boiler-plate code to have a derived class implement constructors that simply call the base class' constructors.

Background

If you write a class but don't provide a constructor, the compiler will provide a parameterless constructor for it. If you derive another class from that class and again don't provide a constructor, the compiler will provide a parameterless constructor for it, this constructor will also call the base class' constructor.

If you write a class that has a constructor that takes a parameter, the derived class must also provide a constructor that calls that constructor (the compiler won't add one). In some class hierarchies (as with custom Exceptions and EventArgs), the constructors of the derived classes don't add any functionality, but merely pass the parameters along to the base class constructors.

C#
public class MyBase
{
    public MyBase
    (
        string Name
    )
    {
        ...
    }
}

public class MyDerived
{
    public MyDerived : MyBase
    (
        string Name
    )
    : base
    (
        Name
    )
    {
    }
} 

In cases like this, it seems to me that the compiler could add the constructor, but it won't.

Because I have had to write a lot of constructors like this, and recently I was about to write a bunch more, I finally got around to devising some simple code generation to do it.

This technique uses an XML file to give the Transsubstantiate utility instructions on what Assembly to load and which class to query for constructors. Information about the constructors is stored in the XML file and then XSLT can be used to transform the information to code. I include some XSLT that will produce partial classes in C# or Visual Basic; you can modify the XSLT to suit your needs.

For convenience, I also added the ability to copy XML documentation for the class and constructors. This feature is rather rudimentary and I haven't bothered to write the XSLT to do it for Visual Basic.

Using the Code - Part 1

First, write the XML file (e.g. MyDerived.xml):

XML
<Transsubstantiate Assembly="MyLibrary.dll" 
	XmlDocumentation="MyLibrary.xml" Type="MyLibrary.MyBase">
  <Produce Namespace="MyLibrary" ClassName="MyDerived" />
</Transsubstantiate>

Second, execute the utility:

Transsubstantiate MyDerived.xml

This will update the XML file and you can then use XSLT to transform it. To make it easier to use, you can specify an XML stylesheet (XSLT) in the XML file and then Transsubstantiate will perform the transform:

XML
<?xml-stylesheet type="text/xsl" href="Transsubstantiate.xsl#cs"?>
<Transsubstantiate Assembly="MyLibrary.dll" 
	XmlDocumentation="MyLibrary.xml" Type="MyLibrary.MyBase">
  <Produce Namespace="MyLibrary" ClassName="MyDerived" /></Transsubstantiate>

In which case, the resultant code will be streamed to the console and you can redirect it to a file:

Transsubstantiate MyDerived.xml > MyDerived.cs

The above XML file specifies the XmlDocumentation attribute of the Transsubstantiate element, this attribute is optional. If included, Transsubstantiate will attempt to copy XML documentation from the specified file, otherwise it won't (XML documentation is not available via Reflection).

Another optional attribute is that the Produce element may have a BaseClass attribute. If specified, the provided XSLT will include its contents as the base class of the resultant class. This is generally not needed if you are also writing another part of the partial class that will specify the base class, but may be used if you are not adding any other code to the derived class.

Transsubstantiate

Main

The Main method of the utility loads the XML document from the specified file, loads the XML Documentation file (if specified), passes them both to the DoTranssubstantiate method, writes the altered XML document back out to overwrite the original file, and, if the document specifies a stylesheet, attempts to transform the XML document to the console.

C#
[System.STAThreadAttribute()]
public static int
Main
(
    string[] args
)
{
    int result = 0 ;

    try
    {
        if ( args.Length > 0 )
        {
            System.IO.FileInfo fi = PIEBALD.Lib.LibFil.GetExpandedFileInfo ( args [ 0 ] ) ;

            System.Xml.XmlDocument doc = PIEBALD.Lib.LibXml.LoadXmlDocument ( fi ) ;

            System.Xml.XmlDocument xml = null ;

            System.Xml.XmlAttribute att ;

            if ( ( att = doc.DocumentElement.Attributes [ "XmlDocumentation" ] ) != null )
            {
                xml = PIEBALD.Lib.LibXml.LoadXmlDocument ( att.Value ) ;
            }

            result += DoTranssubstantiate ( doc , xml ) ;

            PIEBALD.Lib.LibXml.WriteXmlDocument ( doc , fi ) ;

            System.Xml.XmlElement xsl = PIEBALD.Lib.LibXsl.GetStylesheet ( doc ) ;

            if ( xsl != null )
            {
                System.Console.WriteLine ( PIEBALD.Lib.LibXsl.Transform ( doc  , xsl ) ) ;
            }
        }
        else
        {
            System.Console.WriteLine ( "Syntax: Transsubstantiate xmlfile" ) ;
        }
    }
    catch ( System.Exception err )
    {
        System.Console.WriteLine ( err ) ;
    }

    return ( result ) ;
}

DoTranssubstantiate

This method loads the specified Assembly and gets a reference to the specified Type. It then stores any access modifies (public, private, etc.) the Type has to the XML document, calls DrinkTheWine to copy any XML documentation on the class to the XML document, and calls ChewTheWafer to access the Type's constructors and write them to the XML document.

C#
private static int
DoTranssubstantiate
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlDocument XmlDocument
)
{
    int result = 0 ;

    System.Xml.XmlElement tran = Document.DocumentElement ;

    System.Reflection.Assembly assm = PIEBALD.Lib.LibSys.DynamicLoad
        ( tran.Attributes [ "Assembly" ].Value ) ;

    System.Type type = assm.GetType
        ( tran.Attributes [ "Type" ].Value ) ;

    if ( type == null )
    {
        throw ( new System.InvalidOperationException
        (
            "No type named " + tran.Attributes [ "Type" ].Value +
            " found in assembly " + tran.Attributes [ "Assembly" ].Value
        ) ) ;
    }

    System.Xml.XmlNode ele = tran.SelectSingleNode ( "Attributes" ) ;

    if ( ele == null )
    {
        tran.AppendChild ( ele = Document.CreateElement ( "Attributes" ) ) ;
    }

    ele.InnerText = PIEBALD.Lib.LibSys.GetPrefices ( type.Attributes ) ;

    DrinkTheWine
    (
        Document
    ,
        XmlDocument
    ,
        tran
    ,
        "/doc/members/member[@name='T:" + tran.Attributes [ "Type" ].Value + "']"
    ) ;

    result += ChewTheWafer
    (
        Document
    ,
        XmlDocument
    ,
        type
    ,
        tran
    ) ;

    return ( result ) ;
}

ChewTheWafer

Accesses the Type's constructors and writes their relevant details to the XML document. Information about each non-private constructor, its parameters, and any available XML documentation will be written to the XML document.

The code, as it stands now, may not have support for every feature (e.g. optional parameters), but I haven't yet had a need for more than the basics; if you do, you can modify the code and post a message with your improvements.

C#
private static int
ChewTheWafer
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlDocument XmlDocument
,
    System.Type            Source
,
    System.Xml.XmlNode     Dest
)
{
    int result = 0 ;

    System.Xml.XmlNode cons = Dest.SelectSingleNode ( "Constructors" ) ;

    if ( cons != null )
    {
        cons.RemoveAll() ;
    }
    else
    {
        Dest.AppendChild ( cons = Document.CreateElement ( "Constructors" ) ) ;
    }

    System.Xml.XmlAttribute att = Document.CreateAttribute ( "Count" ) ;

    cons.Attributes.Append ( att ) ;

    System.Text.StringBuilder partypes = new System.Text.StringBuilder() ;

    foreach
    (
        System.Reflection.ConstructorInfo ci
    in
        Source.GetConstructors
        (
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Instance
        )
    )
    {
        if ( !ci.IsPrivate )
        {
            System.Xml.XmlElement con = Document.CreateElement ( "Constructor" ) ;

            att = Document.CreateAttribute ( "Attributes" ) ;

            att.Value = PIEBALD.Lib.LibSys.GetPrefices ( ci.Attributes ) ;

            con.Attributes.Append ( att ) ;

            System.Xml.XmlElement pars = Document.CreateElement ( "Parameters" ) ;

            att = Document.CreateAttribute ( "Count" ) ;

            pars.Attributes.Append ( att ) ;

            partypes.Length = 0 ;

            foreach
            (
                System.Reflection.ParameterInfo pi
            in
                ci.GetParameters()
            )
            {
                System.Xml.XmlElement par = Document.CreateElement ( "Parameter" ) ;

                att = Document.CreateAttribute ( "Attributes" ) ;

                att.Value = pi.Attributes.ToString() ;

                par.Attributes.Append ( att ) ;

                att = Document.CreateAttribute ( "IsByRef" ) ;

                att.Value = pi.ParameterType.IsByRef.ToString() ;

                par.Attributes.Append ( att ) ;

                att = Document.CreateAttribute ( "DataType" ) ;

                att.Value = pi.ParameterType.FullName.Trim ( '&' ) ;

                partypes.AppendFormat ( ",{0}" , att.Value ) ;

                par.Attributes.Append ( att ) ;

                att = Document.CreateAttribute ( "Name" ) ;

                att.Value = pi.Name ;

                par.Attributes.Append ( att ) ;

                pars.AppendChild ( par ) ;
            }

            DrinkTheWine
            (
                Document
            ,
                XmlDocument
            ,
                con
            ,
                "/doc/members/member[@name='M:" + Dest.Attributes
                [ "Type" ].Value + ".#ctor(" + partypes.ToString().Trim ( ',' ) + ")']"
            )  ;

            pars.Attributes [ "Count" ].Value = pars.ChildNodes.Count.ToString() ;

            con.AppendChild ( pars ) ;

            cons.AppendChild ( con ) ;

            result++ ;
        }
    }

    cons.Attributes [ "Count" ].Value = cons.ChildNodes.Count.ToString() ;

    return ( result ) ;
}

DrinkTheWine

This method copies the specified XML documentation from the source XML documentation document to the Transsubstantiate XML document.

It is very simplistic, but it does the minimum to contend with some rather irksome XML.

C#
private static void
DrinkTheWine
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlDocument XmlDocument
,
    System.Xml.XmlNode     Parent
,
    string                 Path
)
{
    System.Xml.XmlNode xml = Parent.SelectSingleNode ( "XmlDocumentation" ) ;

    if ( xml != null )
    {
        xml.RemoveAll() ;
    }
    else
    {
        Parent.AppendChild ( xml = Document.CreateElement ( "XmlDocumentation" ) ) ;
    }

    if ( XmlDocument != null )
    {
        int left = Path.IndexOf ( '`' ) ;

        if ( left > -1 )
        {
            left = Path.IndexOf ( '[' , left ) ;
            int right = Path.LastIndexOf ( ']' , Path.Length - 2 ) ;

            Path = Path.Substring ( 0 , left ) + Path.Substring ( right + 1 ) ;
        }

        System.Xml.XmlNode ele = XmlDocument.SelectSingleNode ( Path ) ;

        if ( ele != null )
        {
            xml.InnerXml = ele.OuterXml
                .Replace ( "=\"E:" , "=\"" )
                .Replace ( "=\"F:" , "=\"" )
                .Replace ( "=\"M:" , "=\"" )
                .Replace ( "=\"N:" , "=\"" )
                .Replace ( "=\"P:" , "=\"" )
                .Replace ( "=\"T:" , "=\"" ) ;
        }
    }

    return ;
}

Using the Code - Part 2

The ZIP file contains all the code required to build the Transsubstantiate utility. There is also Build.bat which can be used to compile it at the command line (modify the DotNet_3_5 as required to reflect the version of the compiler you are using).

I have also included Transsubstantiate.xsl which contains stylesheets to transform the resultant XML to either C# or VB.NET. The stylesheet for C# will copy the XML documentation to the generated code; the stylesheet for VB.NET will not -- I haven't bothered to try to get it to work.

MyException

I included three XML files that are intended to generate a PIEBALD.Types.MyException class that derives from System.Exception. This is a situation where Transsubstantiate can do everything required to produce the desired class, but the XML file must specify the BaseClass attribute of the Produce element for it to work.

  • MyException.xml -- does not specify a stylesheet
  • MyExceptionCS.xml -- specifies the C# stylesheet
  • MyExceptionVB.xml -- specifies the VB.NET stylesheet

History

  • 2011-03-29 First submitted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
BSCS 1992 Wentworth Institute of Technology

Originally from the Boston (MA) area. Lived in SoCal for a while. Now in the Phoenix (AZ) area.

OpenVMS enthusiast, ISO 8601 evangelist, photographer, opinionated SOB, acknowledged pedant and contrarian

---------------

"I would be looking for better tekkies, too. Yours are broken." -- Paul Pedant

"Using fewer technologies is better than using more." -- Rico Mariani

"Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’" -- Steve McConnell

"Every time you write a comment, you should grimace and feel the failure of your ability of expression." -- Unknown

"If you need help knowing what to think, let me know and I'll tell you." -- Jeffrey Snover [MSFT]

"Typing is no substitute for thinking." -- R.W. Hamming

"I find it appalling that you can become a programmer with less training than it takes to become a plumber." -- Bjarne Stroustrup

ZagNut’s Law: Arrogance is inversely proportional to ability.

"Well blow me sideways with a plastic marionette. I've just learned something new - and if I could award you a 100 for that post I would. Way to go you keyboard lovegod you." -- Pete O'Hanlon

"linq'ish" sounds like "inept" in German -- Andreas Gieriet

"Things would be different if I ran the zoo." -- Dr. Seuss

"Wrong is evil, and it must be defeated." –- Jeff Ello

"A good designer must rely on experience, on precise, logical thinking, and on pedantic exactness." -- Nigel Shaw

“It’s always easier to do it the hard way.” -- Blackhart

“If Unix wasn’t so bad that you can’t give it away, Bill Gates would never have succeeded in selling Windows.” -- Blackhart

"Use vertical and horizontal whitespace generously. Generally, all binary operators except '.' and '->' should be separated from their operands by blanks."

"Omit needless local variables." -- Strunk... had he taught programming

Comments and Discussions

 
-- There are no messages in this forum --