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:
- Iterate the Rows of the DataTable.
- Create an instance of the class.
- Set the properties with values from the table. Assume that column names and types match property names and types.
- 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:
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:
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.
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
(
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.
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 ] ;
}
{
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:
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:
System.Collections.Generic.List<NamespaceA.ClassA> list = dt.ToList<NamespaceA.ClassA>() ;
History
2014-12-06 First submission