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

Dynamic... But Fast: The Tale of Three Monkeys, A Wolf and the DynamicMethod and ILGenerator Classes

By , 12 Jun 2012
 

Monkey Number One

Once upon a time, there were three little code monkeys. Monkey number one worked at the straw factory out on 7th and Penn. One day the first monkey's boss, B. B. Wolf, brought him a new assignment. The HR department's application was being upgraded to C#. Monkey number one's job was to write the code to populate the Person class with data from the database. The monkey got straight to work and quickly produced code similar to the following (the actual code could not be used for legal reasons):

C#

public class ManualBuilder
{
    public Person Build(SqlDataReader reader)
    {
        Person person = new Person();

        if (!reader.IsDBNull(0))
        {
            person.ID = (Guid)reader[0];
        }

        if (!reader.IsDBNull(1))
        {
            person.Name = (string)reader[1];
        }

        if (!reader.IsDBNull(2))
        {
            person.Kids = (int)reader[2];
        }

        if (!reader.IsDBNull(3))
        {
            person.Active = (bool)reader[3];
        }

        if (!reader.IsDBNull(4))
        {
            person.DateOfBirth = (DateTime)reader[4];
        }

        return person;
    }
}

VB

Public Class ManualBuilder

    Public Function Build(ByVal reader As SqlDataReader) As Person
        Dim person As Person = New Person()

        If Not reader.IsDBNull(0) Then
            person.ID = CType(reader(0), Guid)
        End If

        If Not reader.IsDBNull(1) Then
            person.Name = CType(reader(1), String)
        End If

        If Not reader.IsDBNull(2) Then
            person.Kids = CType(reader(2), Integer)
        End If

        If Not reader.IsDBNull(3) Then
            person.Active = CType(reader(3), Boolean)
        End If

        If Not reader.IsDBNull(4) Then
            person.DateOfBirth = CType(reader(4), DateTime)
        End If

        Return person
    End Function

End Class

This code worked well at first. It was clean and really fast. However, the head of the HR department decided that the new software should really have a few additional features. Every new feature seemed to either require adding fields to the Person table, creating a new table, or moving fields from the Person table to one of the new tables. Every time a new feature was introduced, the monkey had to either edit his code or write mapping code for the new table. It seemed that the monkey was constantly the bottleneck for any new feature being worked on. One day, Mr. Wolf called the monkey into his office. Mr. Wolf huffed, and puffed, and laid the monkey off.

Monkey Number Two

Later, Mr. Wolf was let go from the straw factory due to allegations of improper conduct with Mrs. Pig. He ended up taking a new job at the lumber yard, which just happened to be where monkey number two worked. Mr. Wolf was hired by the lumber yard specifically because of his experience with upgrading HR applications which, interestingly, was exactly the type of project that the lumber yard was about to start. Unsurprisingly, monkey number two was given the task of writing the code to populate the Person class with the data from the database. Mr. Wolf informed monkey number two of the first monkey's fate and not-so-subtly implied that this would also be monkey number two's fate if he did not come up with a more flexible solution. The monkey thought about it for a while and produced something similar to the following:

C#

public class ReflectionBuilder<t>
{
    private PropertyInfo[] properties;

    private ReflectionBuilder() { }

    public T Build(SqlDataReader reader)
    {
        T result = (T)Activator.CreateInstance(typeof(T));

        for (int i = 0; i < reader.FieldCount; i++)
        {
            if (properties[i] != null && !reader.IsDBNull(i))
            {
                properties[i].SetValue(result, reader[i], null);
            }
        }

        return result;
    }

    public static ReflectionBuilder<t> CreateBuilder(SqlDataReader reader)
    {
        ReflectionBuilder<t> result = new ReflectionBuilder<t>();

        result.properties = new PropertyInfo[reader.FieldCount];
        for (int i = 0; i < reader.FieldCount; i++)
        {
            result.properties[i] = typeof(T).GetProperty(reader.GetName(i));
        }

        return result;
    }
}</t>

VB

Public Class ReflectionBuilder(Of T)
    Private properties() As PropertyInfo

    Private Sub ReflectionBuilder()

    End Sub

    Public Function Build(ByVal reader As SqlDataReader) As T
        Dim result As T = CType(Activator.CreateInstance(GetType(T)), T)
        Dim i As Integer

        For i = 0 To reader.FieldCount - 1
            If Not properties(i) Is Nothing And Not reader.IsDBNull(i) Then
                properties(i).SetValue(result, reader(i), Nothing)
            End If
        Next

        Return result
    End Function

    Public Shared Function CreateBuilder(ByVal reader As SqlDataReader) _
            As ReflectionBuilder(Of T)
        Dim result As ReflectionBuilder(Of T) = New ReflectionBuilder(Of T)()
        Dim i As Integer
        ReDim result.properties(0 To reader.FieldCount)

        For i = 0 To reader.FieldCount - 1
            result.properties(i) = GetType(T).GetProperty(reader.GetName(i))
        Next

        Return result

    End Function

End Class

This solution worked much better than the first monkey's solution. As you might have guessed, the requirements for the HR "upgrade" were constantly changing. "Add this feature," "remove that feature," "move this here," "move that there." None of this seemed to matter. Monkey number two's use of reflection meant that his code could automatically recognize the changes. Better yet, when new tables and objects were created, the same code could be used with no additional changes. All was going extremely well. Monkey number two felt certain that he was in line for a major promotion.

But then the unthinkable happened… the application went live. Suddenly Mr. Wolf was inundated with calls from unhappy HR employees complaining about how slooooow the new application was. After a few weeks, Mr. Wolf was let go for his incompetence and the lumber yard went back to using their old HR software. However, Mr. Wolf did manage to terminate monkey number two on his way out.

Monkey Number Three

Despite his apparent ineptitude as a project manager, Mr. Wolf quickly landed a new job down at the brick yard. The brick yard was in the process of migrating their old HR software and felt they could benefit from Mr. Wolf's "expertise." Coincidentally, the brick yard was also the employer of monkey number three. Once again, Mr. Wolf assigned the task of loading the Person class with data from the database to monkey number three and again he implied that the monkey's continued employment depended on not creating either of the issues that monkeys number one and two had created. Monkey number three did some research and stumbled upon the DynamicMethod and ILGenerator classes in .NET 2.0. These classes would allow the monkey to dynamically create and compile code at runtime. This would give him the best of both worlds. His code could be dynamic like monkey number two's, but since it was actually compiled, it would be as fast as monkey number one's.

He did some experimenting. The downside was that the dynamic code needed to be written using IL (intermediate language) instead of C#. However, with a small amount of Googling, some code decompiling using ildasm.exe from the .NET SDK, and some good old trial and error, the monkey was able to create code similar to the following:

C#

public class DynamicBuilder<T>
{
    private static readonly MethodInfo getValueMethod = 
        typeof(IDataRecord).GetMethod("get_Item", new Type[] { typeof(int) });
    private static readonly MethodInfo isDBNullMethod = 
        typeof(IDataRecord).GetMethod("IsDBNull", new Type[] { typeof(int) });
    private delegate T Load(IDataRecord dataRecord);
    private Load handler;

    private DynamicBuilder() { }

    public T Build(IDataRecord dataRecord)
    {
        return handler(dataRecord);
    }

    public static DynamicBuilder<T> CreateBuilder(IDataRecord dataRecord)
    {
        DynamicBuilder<T> dynamicBuilder = new DynamicBuilder<T>();

        DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(T), 
                new Type[] { typeof(IDataRecord) }, typeof(T), true);
        ILGenerator generator = method.GetILGenerator();

        LocalBuilder result = generator.DeclareLocal(typeof(T));
        generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
        generator.Emit(OpCodes.Stloc, result);

        for (int i = 0; i < dataRecord.FieldCount; i++)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(dataRecord.GetName(i));
            Label endIfLabel = generator.DefineLabel();

            if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
            {
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, isDBNullMethod);
                generator.Emit(OpCodes.Brtrue, endIfLabel);

                generator.Emit(OpCodes.Ldloc, result);
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, getValueMethod);
                generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
                generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());

                generator.MarkLabel(endIfLabel);
            }
        }

        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ret);

        dynamicBuilder.handler = (Load)method.CreateDelegate(typeof(Load));
        return dynamicBuilder;
    }
}

VB

Public Class DynamicBuilder(Of T)
    Private Shared ReadOnly getValueMethod As MethodInfo = _
        GetType(IDataRecord).GetMethod("get_Item", New Type() {GetType(Integer)})
    Private Shared ReadOnly isDBNullMethod As MethodInfo = _
        GetType(IDataRecord).GetMethod("IsDBNull", New Type() {GetType(Integer)})
    Private Delegate Function Load(ByVal dataRecord As IDataRecord) As T
    Private handler As Load

    Private Sub DynamicBuilder()

    End Sub

    Public Function Build(ByVal dataRecord As IDataRecord) As T
        Return handler(dataRecord)
    End Function


    Public Shared Function CreateBuilder(ByVal dataRecord As IDataRecord) _
        As DynamicBuilder(Of T)
        Dim dynamicBuilder As DynamicBuilder(Of T) = New DynamicBuilder(Of T)()
        Dim i As Integer

        Dim method As DynamicMethod = New DynamicMethod("DynamicCreate", GetType(T), _
            New Type() {GetType(IDataRecord)}, GetType(T), True)
        Dim generator As ILGenerator = method.GetILGenerator()

        Dim result As LocalBuilder = generator.DeclareLocal(GetType(T))
        generator.Emit(OpCodes.Newobj, GetType(T).GetConstructor(Type.EmptyTypes))
        generator.Emit(OpCodes.Stloc, result)

        For i = 0 To dataRecord.FieldCount - 1
            Dim propertyInfo As PropertyInfo = _
                GetType(T).GetProperty(dataRecord.GetName(i))
            Dim endIfLabel As Label = generator.DefineLabel()

            If Not propertyInfo Is Nothing Then
                If Not propertyInfo.GetSetMethod() Is Nothing Then
                    generator.Emit(OpCodes.Ldarg_0)
                    generator.Emit(OpCodes.Ldc_I4, i)
                    generator.Emit(OpCodes.Callvirt, isDBNullMethod)
                    generator.Emit(OpCodes.Brtrue, endIfLabel)

                    generator.Emit(OpCodes.Ldloc, result)
                    generator.Emit(OpCodes.Ldarg_0)
                    generator.Emit(OpCodes.Ldc_I4, i)
                    generator.Emit(OpCodes.Callvirt, getValueMethod)
                    generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i))
                    generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod())

                    generator.MarkLabel(endIfLabel)
                End If
            End If
        Next

        generator.Emit(OpCodes.Ldloc, result)
        generator.Emit(OpCodes.Ret)

        dynamicBuilder.handler = CType(method.CreateDelegate(GetType(Load)), Load)
        Return dynamicBuilder
    End Function
End Class

Mr. Wolf was skeptical, so monkey number three did his best to explain what was going on.

The first few lines of CreateBuilder instantiate the DynamicMethod and ILGenerator classes. In short, it's creating a new static method called DynamicCreate and adding that method to the object type that was passed in, i.e. the Person class in this example. The method takes SqlDataReader and returns an instance of the correct object. If this were non-dynamic code, you might call it like this:

C#

Person myPerson = Person.DynamicCreate(mySqlDataReader);

VB

dim myPerson as Person = Person.DynamicCreate(mySqlDataReader)

The next line of code generates a variable of the generic type. So this,

LocalBuilder result = generator.DeclareLocal(typeof(T));

in non-dynamic code would be this:

C#

Person myPerson;

VB

Dim myPerson as Person

The next piece of code instantiates the requested type of object and stores it in the local variable.

generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
generator.Emit(OpCodes.Stloc, result);

In non-dynamic code, it would be this:

C#

myPerson = new Person();

VB

myPerson = new Person()

The code then loops through the fields in the data reader, finding matching properties on the type passed in. When a match is found, the code checks to see if the value from the data reader is null.

C#

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, isDBNullMethod);
generator.Emit(OpCodes.Brtrue, endIfLabel);

...

generator.MarkLabel(endIfLabel);

or

if (!mySqlDataReader.IsDBNull(1))
{
    ...
}

VB

If Not mySqlDataReader.IsDBNull(1) Then
    ...
End If

If the value in the data reader is not null, the code sets the value on the object.

generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, getValueMethod);
generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());

Again, in non-dynamic code, it would be this:

C#

myPerson.Name = (string)mySqlDataReader[1];

VB

myPerson.Name = CType(mySqlDataReader(1), string)

The last part of the code returns the value of the local variable:

C#

generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ret);

or

return myPerson;

VB

return myPerson

The code then returns a handler to a delegate. When this handler is invoked, it calls the dynamically generated code, which can be seen in this code:

C#

public T Build(SqlDataReader reader)
{
    return handler(reader);
}

VB

Public Function Build(ByVal dataRecord As IDataRecord) As T
    Return handler(dataRecord)
End Function

Mr. Wolf had no clue what any of this meant, but not wanting to look dumb, he said, "Sounds promising, but let's get some benchmarks before we move forward." Monkey number three quickly threw together a test to use all three approaches. Each sample would load three million rows out of the Person table. Here were the results:

Screenshot - Perf.jpg

Based on these results, Mr. Wolf had monkey number three implement his solution. Development went great. The live release went even better. The project was a huge success. It performed well and came in close to budget. Mr. Wolf received a huge bonus, retired early, and moved to a small private island. Monkey number three was later downsized and is currently unemployed.

Keep It Simple, Monkey

NOTE: This article is an extreme over-simplification. The code is intended to be an introduction to dynamic runtime code generation, not a full-blown solution. That being said, if you carefully and judiciously apply the ideas presented here, you should be able to be just as successful as monkey number three.

Historical Monkeys

  • 7th July, 2007 -- Original article version posted
  • 13th July, 2007 -- Article edited and moved to the main CodeProject article base
  • 24th July, 2007 -- Updated
  • 20th July, 2008 -- Updated to handle DBNull, to handle any IDataRecord (instead of just SqlDataReader), and added VB

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

Herbrandson
Software Developer (Senior) Scratch Audio
United States United States
No Biography provided
Follow on   Twitter

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

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberUrs Wedershoven29-May-13 2:57 
Great Article!
GeneralMy vote of 5memberjohannesnestler11-Apr-13 1:49 
Excellent Story! I've managed to implement nearly the same thing in a project. I also had to tranlate the inner workings from code-monkey to manager... Wink | ;-)
GeneralMy vote of 5memberjfriedman14-Mar-13 6:49 
Great article!
GeneralMy vote of 5memberIvaylo5ev13-Nov-12 22:06 
I've never expected to enjoy a technical read so much! Good work on the code and admirations for the article!
GeneralRe: My vote of 5memberHerbrandson14-Nov-12 5:50 
Thanks Smile | :)
http://software.herbrandson.com

GeneralMy vote of 5mvpKanasz Robert5-Nov-12 2:43 
good article
GeneralMy vote of 5memberBadassAlien18-Oct-12 0:24 
Very nice article, I specially enjoyed the monkey stories Smile | :)
GJ!
Questiongoodmembertsingroo2-Jul-12 21:59 
that is very cool
GeneralMy vote of 5memberTerranceSmith20-Jun-12 5:59 
Nice concept and story. I ended up reading the whole thing without realizing it.
GeneralMy vote of 5memberHaBiX14-Jun-12 20:29 
True story! I also heard Monkey 2.9 produced its dynamic code in c# language (string) and he called the c# compiler to generate the assembly in runtime coz. IL is too time consuming.
GeneralMy vote of 5memberTim Corey13-Jun-12 9:43 
Hey, great article. I appreciate the look into how to continue to improve this scenario. This translates well into a foundation for real-world applications.
SuggestionSince DynamicBuilder is generic (and other suggestions)...memberkornman0012-Jun-12 14:02 
Since DynamicBuilder is generic, those two static MethodInfo variables will be created for every instance where DynamicBuilder<T> is used. Meaning DynamicBuilder<Person> and DynamicBuilder<Monkey> would have their own unique instances of those MethodInfo vars (even though they aren't used uniquely, ie Person and Monkey wouldn't require two different MethodInfos). Though, looking at the attached code (which slightly differs from the code seen in the article) I see you create these MethodInfos as temporaries within CreateBuilder...which is just unnecessary overhead with Reflection.
 
You could just make DynamicBuilder non-generic (ie, without the type param), but keep CreateBuilder generic and have return a Func<IDataRecord, T>[^]. Using a class which just proxies to a dynamic method is just unneeded overhead IMO (unless there was more to the design/class, which may be the case in your business implementation), but don't think I'm bashing your work, it's still good stuff Smile | :)
 
Also, I would suggest looking into LINQ Expressions. I don't see anything to suggest you can't generate this with Expressions instead (thus avoiding IL Smile | :) !)
 
Cheers
GeneralMy vote of 5mvpPaulo Zemek12-Jun-12 11:13 
I myself have a similar implementation to this one, and I consider it a great example on how to generate code dynamically! Excellent!
GeneralMy vote of 5memberBadassAlien12-Jun-12 4:49 
Love the story and the simplistic approach to the subject
Generalmore than wordmemberwitsanukam11-Jun-12 18:40 
55555 star +++++ Laugh | :laugh:
thank.
Questionabout your code with cpol license [modified]memberqsmy1237-Jun-12 23:22 
if i change your code about 'DynamicBuilder' and then Integrate with my own project,
Is that mean I must publish my Project's source code????
 
below is the changed source code
<pre lang="c#">using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
 
namespace Moon.Orm
{
	/// <summary>
	/// 运用 DynamicMethod and ILGenerator 创建实体
	/// CPOL开源协议
	/// 作者:Herbrandson
       ///changed:add a dicationary as a cache to improve speed. 2012-6-9  秦仕川
	/// </summary>
	/// <typeparam name="T"></typeparam>
	internal class DynamicBuilder<T>
	{
		// http://www.codeproject.com/info/cpol10.aspx
		private static readonly MethodInfo getValueMethod = typeof(IDataRecord).GetMethod("get_Item", new Type[] { typeof(int) });
		private static readonly MethodInfo isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull", new Type[] { typeof(int) });
		
		private DynamicBuilder() { }
		/// <summary>
		/// 该委托由动态IL调用
		/// </summary>
		private delegate T Load(IDataRecord dataRecord);
		private Load handler;
		
		/// <summary>
		/// 执行CreateBuilder里创建的DynamicCreate动态方法的委托
		/// </summary>
		/// <param name="dataRecord"></param>
		/// <returns></returns>
		public T Build(IDataRecord dataRecord)
		{
			return handler(dataRecord);
		}
		private static object _LOCK=new object();
		private static Dictionary<int,object> _DELEGATE_MAP=new Dictionary<int, object>();
		/// <summary>
		/// 得到一个创建者
		/// </summary>
		/// <param name="dataRecord"></param>
		/// <returns></returns>
		public static DynamicBuilder<T> CreateBuilder(IDataRecord dataRecord)
		{
			int entityTypeHashCode=typeof(T).GetHashCode();
			lock(_LOCK){
				if (_DELEGATE_MAP.ContainsKey(entityTypeHashCode)) {
					return _DELEGATE_MAP[entityTypeHashCode] as DynamicBuilder<T>;
				}
			}
			
			DynamicBuilder<T> dynamicBuilder = new DynamicBuilder<T>();
			//定义一个名为'DynamicCreate'的动态方法,返回值typof(T),参数typeof(IDataRecord),与typeof(IDataRecord)逻辑关联{动态方法可以访问类型的所有成员。 },
			DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(IDataRecord) }, typeof(T), true);
			//创建一个MSIL生成器,为动态方法生成代码
			ILGenerator generator = method.GetILGenerator();
			//为动态方法声明指定类型T的局部变量 T result;
			LocalBuilder result = generator.DeclareLocal(typeof(T));
			//The next piece of code instantiates the requested type of object and stores it in the local variable.
			//可以t=new T();这么理解
			generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
			generator.Emit(OpCodes.Stloc, result);
 
			for (int i = 0; i < dataRecord.FieldCount; i++)//数据集合,熟悉的for循环 要干什么你懂的
			{
				PropertyInfo propertyInfo = typeof(T).GetProperty(dataRecord.GetName(i));//根据列名取属性  原则上属性和列是一一对应的关系
				Label endIfLabel = generator.DefineLabel();
 
				if (propertyInfo != null && propertyInfo.GetSetMethod() != null)//实体存在该属性 且该属性有SetMethod方法
				{
					/*The code then loops through the fields in the data reader, finding matching properties on the type passed in.
					 * When a match is found, the code checks to see if the value from the data reader is null.
					 */
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldc_I4, i);
					generator.Emit(OpCodes.Callvirt, isDBNullMethod);//就知道这里要调用IsDBNull方法 如果IsDBNull==true contine
					generator.Emit(OpCodes.Brtrue, endIfLabel);
					/*If the value in the data reader is not null, the code sets the value on the object.*/
					generator.Emit(OpCodes.Ldloc, result);
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldc_I4, i);
					generator.Emit(OpCodes.Callvirt, getValueMethod);//调用get_Item方法
					generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
					generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());//给该属性设置对应值
					generator.MarkLabel(endIfLabel);
				}
			}
 
			/*The last part of the code returns the value of the local variable*/
			generator.Emit(OpCodes.Ldloc, result);
			generator.Emit(OpCodes.Ret);//方法结束,返回

			//完成动态方法的创建,并且创建执行该动态方法的委托,赋值到全局变量handler,handler在Build方法里Invoke
			dynamicBuilder.handler = (Load)method.CreateDelegate(typeof(Load));
			lock(_LOCK){
				if (_DELEGATE_MAP.ContainsKey(entityTypeHashCode)==false) {
					_DELEGATE_MAP.Add(entityTypeHashCode,dynamicBuilder);
				}
			}
			return dynamicBuilder;
		}
	}
}


modified 9-Jun-12 3:37am.

AnswerRe: about your code with cpol licensememberHerbrandson11-Jun-12 11:36 
I've updated the license to be MS-PL. Let me know if that resolves any problems for you. In short, my intent is for you to be able to use the provided code in any way you like.
http://software.herbrandson.com

GeneralRe: about your code with cpol licensememberqsmy12311-Jun-12 14:32 
wow,thank you every much!
In short, my intent is for you to be able to use the provided code in any way you like.
GeneralRe: about your code with cpol licensememberqsmy12311-Jun-12 14:39 
wow,thank you every much!
In short, my intent is for you to be able to use the provided code in any way you like.
but i want to say:ms-pl is not every good to me.....
GeneralRe: about your code with cpol licensememberqsmy12311-Jun-12 14:40 
wow,thank you every much!
In short, my intent is for you to be able to use the provided code in any way you like.
but i want to say:ms-pl is not every good for me.....
SuggestionRe: about your code with cpol licensememberqsmy12311-Jun-12 15:09 
Could you change your code's license with BSD License???
thank youSmile | :)
GeneralRe: about your code with cpol licensememberHerbrandson12-Jun-12 5:48 
Done
http://software.herbrandson.com

GeneralRe: about your code with cpol licensememberqsmy12312-Jun-12 12:35 
thank you Smile | :)
GeneralMy vote of 5memberTomas Palmer20-Jan-12 8:02 
I needed a solution for the problem of having to tune code dynamically (learning alg) driven by a broad set of data real-time data. This article was a great help
GeneralMonkey 3.1? [modified]membershingokw24-May-11 7:11 
Nice solution!
 
But maybe it can be faster, i would like to say a few points:
 
- You could use a Dictionary, linking property type and the respective IDataReader.GetXXX method, and this way, avoiding box and unbox in the generic GetValue.
For example, in properties with the type int, use IDataReader.GetInt32(int i).
 
- And also, checking if the property type is nullable (reference type or Nullable<>), to avoid checking if the value is null in the database.
 
- Use implicit get and set ( public int Value { get; set;} ) and take advantage of the access protection bypass of ILGenerator,
marked as true in your example, (restrictedSkipVisibility in DynamicMethod constructor).
Just check if <PropertyName>k__BackingField exists (where PropertyName is the property name :P), and use OpCodes.Stfld to set the field directly.
 
- And maybe use a singleton aproach to avoid the method creation, since it's dynamic but also static. Wink | ;)
 
Sorry about the lame english. Smile | :)
 
[EDIT] Oops, sorry, now i saw the "Keep It Simple, Monkey" note :$
modified on Tuesday, May 24, 2011 1:34 PM

GeneralRe: Monkey 3.1?memberdelimavi9-Jun-11 4:26 
it would be nice if you can add your advices to codes. So we can see how and what.
GeneralFantastic stuff.memberLeigh Pointer5-Apr-11 23:18 
I have executed the code with no errors, but when running in a web app as a referenced assembly I set this error Frown | :(
 
Common Language Runtime detected an invalid program. ->; System.InvalidProgramException: Common Language Runtime detected an invalid program. at DynamicCreate(IDataRecord ) at DynamicBuilder`1.Build(IDataRecord dataRecord)
 
It appears to be bombing on the method:
 
public T Build(IDataRecord dataRecord)
{
return handler(dataRecord);
}
GeneralRe: Fantastic stuff.memberHerbrandson6-Apr-11 7:58 
Hmmmmm, I think i'd need a few more details. What is the class you are trying to load?
 
Also, the provided code isn't really meant to be production code. It's intent is to explain the concepts behind runtime IL generation. You'll want to extend/test it a fair amount if you intend to use it in a production system.
http://software.herbrandson.com

GeneralRe: Fantastic stuff.memberLeigh Pointer6-Apr-11 21:03 
First, thanks for the reply.
Your post pointed me to lots of googleing for IL generation which inturn has throw me a mass of ideas.
Your concept runs just fine out of the box and is a good place to start building, which I did, but no matter what class I load it bombs Frown | :( I am loading a class from another assembly. I am drawing on the fact that it might be something to do with the app domain and security.
GeneralRe: Fantastic stuff.memberLeigh Pointer6-Apr-11 22:45 
Cracked it!

Dim method As DynamicMethod = New DynamicMethod("DynamicCreate", GetType(T), New Type() {GetType(IDataRecord)}, GetType(T), False)
Changed to
Dim method As DynamicMethod = New DynamicMethod("DynamicCreate", GetType(T), New Type() {GetType(IDataRecord)}, Assembly.GetExecutingAssembly().[GetType]().[Module], True)
 

DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(IDataRecord) }, typeof(T), false);
Changed to
DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(IDataRecord) }, Assembly.GetExecutingAssembly().GetType().Module, true);
GeneralRe: Fantastic stuff.memberHerbrandson7-Apr-11 5:52 
Excellent
http://software.herbrandson.com

GeneralStrange resultsmemberDizZ28-Jul-10 4:40 
Here are the results I get from a table containing 500K rows on my SQL Server
 
Start Manual Load: 2010-07-28 10:38:38
Stop Manual Load: 2010-07-28 10:38:53
Elapsed Time: 00:00:15.1715837
 
Start Reflection Load: 2010-07-28 10:38:53
Stop Reflection Load: 2010-07-28 10:39:08
Elapsed Time: 00:00:15.0622108
 
Start Dynamic Load: 2010-07-28 10:39:08
Stop Dynamic Load: 2010-07-28 10:39:23
Elapsed Time: 00:00:15.3122060
 
Done!
 
Do you have any idea of what is happening?
GeneralRe: Strange resultsmemberHerbrandson28-Jul-10 6:52 
I'm not sure off hand. It seems strange that the reflection could outperform the manual version. Code you post a code sample?
http://software.herbrandson.com

GeneralRe: Strange resultsmemberDizZ2-Aug-10 4:03 
Here are the changes I have made to your project:
 

public class ManualBuilder
{
public Person Build(SqlDataReader reader)
{
Person person = new Person();
 
if (!reader.IsDBNull(0))
{
person.ID_Part = (Guid)reader[0];
}
 
if (!reader.IsDBNull(1))
{
person.Description = (string)reader[1];
}
 
if (!reader.IsDBNull(2))
{
person.Code = (string)reader[2];
}
 
if (!reader.IsDBNull(3))
{
person.Qty = (double)reader[3];
}
 
return person;
}
}
 
public class Person
{
private Guid m_ID_Part;
public Guid ID_Part
{
get { return m_ID_Part; }
set { m_ID_Part = value; }
}
 
private string m_Description;
public string Description
{
get { return m_Description; }
set { m_Description = value; }
}
 
private string m_Code;
public string Code
{
get { return m_Code; }
set { m_Code = value; }
}
 
private double m_Qty;
public double Qty
{
get { return m_Qty; }
set { m_Qty = value; }
}
}
 
static void Main(string[] args)
{
SqlConnection connection = new SqlConnection(myConnString);
SqlCommand command = new SqlCommand("Select TOP 500000 ID_Part, Description, Code, Qty From Part", connection);
connection.Open();
 
LoadManually(command);
LoadUsingReflection(command);
LoadUsingDynamicCode(command);
 
connection.Close();
Console.WriteLine("Done!");
Console.Read();
}
 

 
Here are the result from another run:
 
Start Manual Load: 2010-08-02 09:54:20
Stop Manual Load: 2010-08-02 09:54:36
Elapsed Time: 00:00:15.3750984
 
Start Reflection Load: 2010-08-02 09:54:36
Stop Reflection Load: 2010-08-02 09:54:51
Elapsed Time: 00:00:15.1094717
 
Start Dynamic Load: 2010-08-02 09:54:51
Stop Dynamic Load: 2010-08-02 09:55:06
Elapsed Time: 00:00:15.1875972
 
Done!
 
The SQL Server is on a remote server running SQL 2005 X64 SP2 and my computer is Intel Core 2 Duo E8400 with 4GB of RAM running XP 32bits SP3. Nothing special...
 
Thank you for your help.
GeneralRe: Strange resultsmemberHerbrandson2-Aug-10 12:43 
I don't see anything fishy off hand. Could you try creating a database schema that matches what's used in the article and try running the original code against it?
http://software.herbrandson.com

GeneralRe: Strange resultsmemberDizZ4-Aug-10 4:25 
I have retried using the Microsoft's AdventureWorks database and I am getting results closer to yours. Thank for your time and for your great article, it will be very useful.
GeneralRe: Strange resultsmemberHerbrandson4-Aug-10 4:56 
No problem. Glad it's working. I wonder what made the other results come out so different?
http://software.herbrandson.com

GeneralRe: Strange resultsmemberjfriedman12-Jun-12 3:57 
Probably some cache or an optimization if he compiled with x86...
GeneralYou still to maintain the Person classmemberAnthonyLondon27-Nov-09 0:29 
Unless I'm missing the point, even with the dynamic approach, you still need to modify the Person class manually if new properties need to be added (or removed) so the HR department will still depend on the client code developer to make those modifications (to reflect the corresponding changes in the database).
 
When I started reading the article I thought you meant the Person class would be generated dynamically based on the fields in the DataReader but it's not the case.
When you execute your code, the Person class needs to exist first:
 
DynamicBuilder < Person > builder = DynamicBuilder < Person > .CreateBuilder(reader);
GeneralRe: You still to maintain the Person classmvpPaulo Zemek12-Jun-12 3:33 
I am not the author, but I will comment.
You are right, it is still needed to maintain a Person class. But specially if LINQ is used, you always need such class.
The advantage is that it is only a matter of adding a new property to the class, instead of also writing code to read from the datareader and then fill the person class.
As an introduction, I think this great. Surely it miss a lot of real-world needs, but is a good introduction.
 
As I already saw in comments, it will be great to call GetInt32 (or equivalent) for specific types. And this solution still miss support for user created types (how will it convert a char value of T or F to bool?). Still, I will give my five to the article.
GeneralRe: You still to maintain the Person classmemberjfriedman12-Jun-12 4:02 
This is not obviously an End All Be All Solution but it does articulate how one can create a single method to handle the loading of entities from the DataBase. With the addition of an Attribute on certain methods you would be able to control the storage type @ the database as well as other things to handle situations such as T or F chars for Bool or XML Types etc....
 
You should add a DynamicSave with the attribute support than support the attributes for load.
GeneralYou are Socrates!memberggraham41226-Oct-09 7:23 
My vote of ... 5!
 
The article was great technically and had a great narrative style! It is so rare to see the two together. It therefore took about five minutes to grasp the significance. Awesome!
 
I'll report that we've done something similar in our own code. We had a business layer comprising several hundred parameters distributed over dozens of classes backing an internal web application. The objects were kept in a memory cache to increase performance, and we implemented dynamic proxies around them to function as tripwires to tell us when an object was dirty. The proxies were dynamically generated in much the same way as you describe. We additionally rely on attributes applied to the business object fields to tell us how to generate the proxy mappings, and we cache the builder types.
GeneralThanks for the articlememberMiguel Barros30-Aug-09 14:07 
This is really a good article. Thank you
 
Miguel Barros - Developer
t3k.pt

QuestionSort delegate for ListSortDescriptionCollection [modified]memberrobajz22-Jul-09 2:59 
Hey! cool article Smile | :)
 
Tried to use it to make a sorting delegate out of ListSortDescriptionCollection that goes along with IBindingListView.
 
Compiles fine, but when executed throws an InvalidProgramException - Common
Language Runtime detected an invalid program.
 
That's a little too short story after the three monkeys Big Grin | :-D How can I get to the bottom of the problem? Is it possible to debug this stuff perhaps? I've found some info here - Executing dynamic IL with DynamicMethod - but it just puzzles me. I see it is a solution "once it's done it rocks", so yeah the bit to get it done is missing...
 
[EDIT] I see now the Comparer should have the "Default" in there, maybe I'll manage fix it...
[EDIT2] Alright panic over... I DID IT!!! It's cool and it rocks! Code below updated...
Yet the question remains - how do you debug this?
 
Here's what I have:
 
    public virtual Func<T, T, int> GetSortFn(ListSortDescriptionCollection sorts)
    {
      var t = typeof(T);
 
      DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(int), new Type[] { t, t }, this.GetType(), true);
      ILGenerator generator = method.GetILGenerator();
 
      LocalBuilder valLeft = generator.DeclareLocal(typeof(object));
      LocalBuilder valRight = generator.DeclareLocal(typeof(object));
      LocalBuilder result = generator.DeclareLocal(typeof(int));
      Label finishedLabel = generator.DefineLabel();
 
      generator.Emit(OpCodes.Ldc_I4, 0);
      generator.Emit(OpCodes.Stloc, result);
 
      foreach (ListSortDescription sort in sorts)
      {
        var direction = sort.SortDirection == ListSortDirection.Ascending ? 1 : -1;
        var propType = sort.PropertyDescriptor.PropertyType;
        var propComparerType = Type.GetType("System.Collections.Generic.Comparer`1[" + propType.FullName + "]");
        var propComparerDefaultGetter = propComparerType.GetProperty("Default").GetGetMethod();
        var compareMethod = propComparerType.GetMethod("Compare", new Type[] { propType, propType });
 
        var propInfo = t.GetProperty(sort.PropertyDescriptor.Name);
        var propGetter = propInfo.GetGetMethod();
 
        LocalBuilder comparer = generator.DeclareLocal(propComparerType);
 
        generator.Emit(OpCodes.Callvirt, propComparerDefaultGetter);
        generator.Emit(OpCodes.Stloc, comparer);
 
        // get the property value of left arg => valLeft
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, propGetter);
        generator.Emit(OpCodes.Stloc, valLeft);
 
        // get the property value of right arg => valRight
        generator.Emit(OpCodes.Ldarg_1);
        generator.Emit(OpCodes.Callvirt, propGetter);
        generator.Emit(OpCodes.Stloc, valRight);
 
        // compare left and right property => result
        generator.Emit(OpCodes.Ldloc, comparer);
        generator.Emit(OpCodes.Ldloc, valLeft);
        generator.Emit(OpCodes.Ldloc, valRight);
        generator.Emit(OpCodes.Callvirt, compareMethod);
        generator.Emit(OpCodes.Stloc, result);
 
        // multiply by sort direction
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ldc_I4, direction);
        generator.Emit(OpCodes.Mul);
        generator.Emit(OpCodes.Stloc, result);
 
        // if the comparison was non-zero go to the end (return result)
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Brtrue, finishedLabel);
      }
 
      // return result
      generator.MarkLabel(finishedLabel);
      generator.Emit(OpCodes.Ldloc, result);
      generator.Emit(OpCodes.Ret);
 
      var srtfn = (Func<T, T, int> )method.CreateDelegate(typeof(Func<T, T, int> ));
 
      return srtfn;
    }
 
Thank you, Rob
 
modified on Wednesday, July 22, 2009 10:44 AM

AnswerRe: Sort delegate for ListSortDescriptionCollectionmemberHerbrandson22-Jul-09 5:10 
Debugging is not really supported. The best thing I've found is to create a little helper to emit the op-codes for using "Console.Write()". Similar to the old school "Alert()" style debugging from javascript.
 
http://software.herbrandson.com

GeneralMy vote of 1memberjatin_chauhan7-Jul-09 21:13 
Na
GeneralRe: My vote of 1memberHerbrandson22-Jul-09 5:07 
And I vote your comment with a 1 Smile | :)
 
http://software.herbrandson.com

GeneralRe: My vote of 1memberMiguel Barros30-Aug-09 14:06 
Me too
 
Miguel Barros - Developer
t3k.pt

GeneralRe: My vote of 1memberggraham41226-Oct-09 7:27 
If you're going to vote "1" at least say why. This was obviously a well written article that took work to put together, and not some random BS.
GeneralMy vote of 1 on your vote of 1memberZac Greve14-Jun-12 14:04 
So you have sodium? Now all we need is some H2O, and.......
 
BOOOOOOOOOOOOOM!!!!!
public class SysAdmin : Employee
{
 
     public override void DoWork(IWorkItem workItem)
     {
          if (workItem.User.Type == UserType.NoLearn){
             throw new NoIWillNotFixYourComputerException(new Luser(workItem.User));
          }else{
               base.DoWork(workItem);
          }
     }
 
}

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130617.1 | Last Updated 12 Jun 2012
Article Copyright 2007 by Herbrandson
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid