Click here to Skip to main content
15,888,527 members
Articles / Database Development
Alternative
Tip/Trick

Generating constructors at compile time

Rate me:
Please Sign up or sign in to vote.
4.72/5 (8 votes)
8 Dec 2014CPOL5 min read 26.2K   8   8
This is an alternative for "C# Convert DataTable to List of Objects Dynamically"

Introduction

This Tip is an example of avoiding heavy use of Reflection at runtime, when runtime Reflection is not truly required. There are situations when Reflection is required at runtime, but it's costly, so one should make the effort to avoid it when it is not required. As part of that, one should cache the information that is gathered so you don't have to get it again.

Another technique, that I present here, is to use Reflection to generate code (I'll show only C# code in this Tip) that can then be added to, and compiled right into, your project to greatly reduce (and in some cases eliminate) the use of Reflection at runtime.

This does assume that you are using your own classes which you are building. It won't work with classes which you are not building, third-party classes for instance.

Something else that runtime Reflection may allow you to do is to access private setters, and while that's generally considered poor form, it may be your only option.

Background

The Tip this one refers to uses Reflection in a way that can easily be avoided. I am writing this as an alternative to that Tip primarily to "put my money where my mouth is".

Although it is working code, it is not actually code I use, I only wrote it today as a demonstration of the ideas I wanted to present. I hope others find a use for the ideas, and maybe someone will actually find it useful. It could form the core of a useful library, but it doesn't have many features.

I also intend to write a full Article that takes this technique even further. (Teaser: It generates all the properties of the class as well.)

The Task

Given a DataTable and a class:

  1. Iterate the Rows of the DataTable.
  2. Create an instance of the class.
  3. Set the properties with values from the table. Assume that column names and types match property names and types.
  4. Add the instance to a List of the class.

The code in the other Tip actually does a little more than that, but this is the minimum requirements as I see them.

 

On a side note, I personally prefer to use a DataReader rather than a DataTable, but this is the hand that I was dealt. Adding similar support for DataReaders is a minor exercise.

Genesis

If you know up front that you'll need to create instances of a class based on DataRows, then you may just start out writing a constructor that accepts a DataRow and performs steps 2 and 3 above. Simple, I do that all the time. But if you already have a bunch of classes developed and then decide you need to create them from DataRows, then suddenly the idea of writing all those constructors may seem daunting. It can be an error-prone exercise (possibly involving lots of copy/paste) and it may seriously increase your maintenance burden -- that should be avoided. Faced with this challenge, many developers decide to use Reflection to, in essence, perform the duties of the missing constructor.

But, if your code can use Reflection to discover what needs to be set, then it can write the missing constructor and the code can be included in the project. Then the use of Reflection is reduced to getting only that one needed constructor which knows how to do the rest. Another option would be to generate a Factory Method for the class, either way should work fine.

This, in my opinion, is an excellent way to make use of partial class definitions.

Sample Class

Consider the following simple class:

C#
namespace NamespaceA
{
    public partial class ClassA
    {
        public int    ID   { get ; private set ; }
        public string Name { get ; private set ; }

        public ClassA ( int ID , string Name ) { this.ID = ID ; this.Name = Name ; }

        public override string ToString() 
            { return System.String.Format ( "ID:{0} Name:{1}" , this.ID , this.Name ) ; }
    }
}

From that, the code I'm about to show has generated this:

C#
// This code was generated by GenerateConstructor<NamespaceA.ClassA>
namespace NamespaceA
{
  public partial class ClassA
  {
    public ClassA ( System.Data.DataRowView Row ) : this
    (
      (System.Int32) Row [ "ID" ] ,
      (System.String) Row [ "Name" ]
    ){}
  }
}

One niggle I have with this constructor is the use of column names rather than ordinals, but I'll let that slide for now. Something I want to point out is that, because it is a constructor for the class, it has proper access to the class' privates; you don't have to cheat to get at them.

GenerateConstructor

Here's the code that generated that constructor. It uses Reflection to get the information it needs and simply formats it into a StringBuilder. Once the calling code -- a command-line utility probably -- has it, it can save it to a file or whatever needs to be done with it.

C#
        public static string GenerateConstructor<T>()
        {
            System.Text.StringBuilder result = new System.Text.StringBuilder() ;

            System.Type typ = typeof(T) ;

            result.AppendFormat ( 
@"
// This was generated by GenerateConstructor<{0}.{1}>
namespace {0}
{{
  public partial class {1}
  {{
    public {1} ( System.Data.DataRowView Row ) : this
    ( " , typ.Namespace , typ.Name ) ;

            foreach 
            ( 
                // GetProperties is _not_ called at application runtime
                // You can filter for Attributes and such if you like
                System.Reflection.PropertyInfo pi in 
                    typ.GetProperties ( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance ) 
            )
            {
                result.AppendFormat ( "\r\n      ({0}) Row [ \"{1}\" ] ," , pi.PropertyType.FullName , pi.Name ) ;
            }

            result.Length-- ;
            
            result.AppendFormat ( "\r\n    ){{}}\r\n  }}\r\n}}\r\n" ) ;

            return ( result.ToString() ) ;
        }
    }

There are many features you can add if you choose to. You can have it access the Attributes on the Properties to alter which ones its uses and what names and types to use. You can have it generate Visual Basic code.

ListOmatic

Here, then, is my alternative to the Tip. The only thing standing in the way of eliminating Reflection is that the implementation of Generics (and Interfaces) doesn't allow the specification of constructors (other than parameterless constructors) and static methods. This may be understandable, but Generics could do so much more if they were allowed to.

The important thing is that, at runtime, Reflection is only used to get that one special constructor. The constructor is also cached for future use. I bolded the loop that does all the actual work involved in the Task.

C#
namespace GenericDataList
{
    using Cache = System.Collections.Generic.Dictionary<System.Type,System.Reflection.ConstructorInfo> ;

    public static class ListOmatic
    {
        private static readonly Cache cache ;
        private static readonly System.Type[] types ;

        static ListOmatic() 
        { 
            cache = new Cache() ; 
            types = new System.Type[] { typeof(System.Data.DataRowView) } ;
        }

        public static System.Collections.Generic.List<T> ToList<T> 
        ( this System.Data.DataTable Table ) 
        { return Table.DefaultView.ToList<T>() ; }

        public static System.Collections.Generic.List<T> ToList<T> 
        ( this System.Data.DataView Rows )
        {
            System.Collections.Generic.List<T> result = new System.Collections.Generic.List<T> ( Rows.Count ) ;

            System.Type typ = typeof(T) ;

            System.Reflection.ConstructorInfo con ;

            lock ( cache )
            {
                if ( cache.ContainsKey ( typ ) ) 
                {
                    con = cache [ typ ] ;
                }
                {
                    // GetConstructor is the only Reflection at runtime
                    con = typ.GetConstructor ( types ) ;

                    if ( con == null ) throw new System.ArgumentException 
                        ( "The generic type doesn't have an appropriate constructor" , typ.FullName ) ;

                    cache [ typ ] = con ;
                }
            }

            System.Data.DataRowView[] parms = new System.Data.DataRowView [ 1 ] ;

            foreach ( System.Data.DataRowView row in Rows ) 
            {
                parms [ 0 ] = row ;
                result.Add ( (T) con.Invoke ( parms ) ) ;
            }

            return ( result ) ;
        }
    }
}

Usage

You don't have to generate all the constructors; you can still hand-write any that you wish. For any constructors that you do wish to generate, you can write a simple utility that includes statements like:

C#
System.Console.WriteLine ( GenericDataList.ListOmatic.GenerateConstructor<NamespaceA.ClassA>() ) ;

Or you could write the code to a TextBox if you fear the console. You could then copy/paste the code to a file or redirect it to a file. How you incorporate the generated code into your project is entirely up to you.

Once you've generated any constructors you need to generate and added them to your project, usage is the same as the original example:

C#
System.Collections.Generic.List<NamespaceA.ClassA> list = dt.ToList<NamespaceA.ClassA>() ;

History

2014-12-06 First submission

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

 
QuestionVery interesting Pin
Jörgen Andersson7-Dec-14 9:57
professionalJörgen Andersson7-Dec-14 9:57 
GeneralMy vote of 1 Pin
Dmitry Alexandrov7-Dec-14 4:27
Dmitry Alexandrov7-Dec-14 4:27 
GeneralRe: My vote of 1 Pin
PIEBALDconsult7-Dec-14 4:38
mvePIEBALDconsult7-Dec-14 4:38 
Questionthread safety problem Pin
Dmitry Alexandrov7-Dec-14 4:18
Dmitry Alexandrov7-Dec-14 4:18 
GeneralRe: thread safety problem Pin
PIEBALDconsult7-Dec-14 4:35
mvePIEBALDconsult7-Dec-14 4:35 
Questionbad solution Pin
Dmitry Alexandrov7-Dec-14 4:05
Dmitry Alexandrov7-Dec-14 4:05 
GeneralRe: bad solution Pin
PIEBALDconsult7-Dec-14 4:34
mvePIEBALDconsult7-Dec-14 4:34 
QuestionNicely done Pin
saddam abu ghaida7-Dec-14 1:13
professionalsaddam abu ghaida7-Dec-14 1:13 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.