Jan 4 2010

Busy Busy Busy!

It’s been a while since my last blog post so I wanted to post a quick update as to where I’ve been. First, I started a new job where I’m porting an iPhone app over to Android. It’s been a challenge brushing up on my c++ and java skills, but very rewarding and I’m enjoying it thoroughly.

Second, I’ve been working on a side project for the last half year that I’m extremely excited about. I’m creating a free, collaborative, online recording studio. It should be launching in the next month or so. You will be able to check it out at http://audioorchard.com.

So that’s the skinny. Hopefully things will slow down some hear in 2010 and I’ll be able to get back to blogging on a more consistent basis. Happy New Year!

  • Share/Save/Bookmark

Sep 11 2009

Faster Silverlight Unit Testing

I’ve recently been working on a project using Silverlight, and while there are many things I really like about Silverlight (like threads in the browser, heck yeah!) one thing that has been killing me is the Silverlight Unit Testing framework. It is painfully slooooooooooow. I currently have somewhere around 160 tests and it’s been taking over 30 seconds to run the suite. That fact, combined with the fact that there isn’t a way to run only one test was actually becoming fairly detrimental. I was finding myself skipping some simple tests just because it didn’t feel worth the wait. Or, I would write and implement multiple tests before running them, just to not lose my train of thought.

So the other day I thought I’d take a crack at building my own unit testing framework. It turned out to be really simple and after a few hours I had something up and running. The best part was that I could now run my 160 unit tests in under 2 seconds. Not too bad if I do say so myself.

Now, to be fair, the Silverlight Unit Testing framework does a lot of things that my little testing framework does not do. Most notably, my framework does not support testing UI elements. However, for my project UI testing is not what I’m most worried about. Hopefully there are others out there who are in a similar situation that will find this helpful.

But wait, there’s more! After doing TDD for a while now, one thing that can be annoying is when a test fails and you get a message like “expected:3 actual:7”. What expected 3? I wrote that test 4 days ago. I don’t remember what was supposed to be “3”. Typically, this problem is solved by adding a “message” to your Assert that might say something like “User.Age should equal 3”, but that requires extra typing and who has time for that? So now that I’d created my own testing framework I thought I’d try out a little experiment using lambdas. Why lambdas? Because there shinny and new. No, because lambdas can actually be used as an expression tree. That means that in addition to being able to execute the lambda, I can parse it out and turn it back into something that fairly closely resembles the original source code. It’s probably easiest to explain with some screen shots. Here are two tests that test the same thing. The first is using the traditional Assert.AreEqual syntax. The second one is using the new and improved lambda syntax.

unit test code

And here are the test results for both of these failing tests. Notice that the first failing test result (the one using the lambda) gives you a lot more detail about what code actually failed. There’s no need for me to dig into the source code to find out what “expected 3 but was 7” means. Even better I still got to be lazy and didn’t have to type a descriptive message into my Assert.

testrun

If you’d like to try it out, I’ve tried to make it as easy as possible. I’ve created some “compatibility” namespaces in order to make it easier to switch frameworks (i.e. all the namespaces/class names/method names for my testing framework match those of the Microsoft one). For many scenarios it should be as easy as removing the reference to the Microsoft Silverlight unit testing framework from your test project and replacing it with a reference to this framework. I haven’t recreated everything from the Microsoft framework, but I think all of the essentials are there. Hopefully this will allow you to quickly assess whether this is a good solution for you or not. Any feedback would be great!

Source

  • Share/Save/Bookmark

Aug 24 2009

CastAs() Extension Method. Should It Stay Or Should It Go?

Have you ever coded something like this…

((Class1)((Class2)item).AProperty().AMethod()

Ok, I don’t mean the names (hopefully you’ve never used those), but doing multiple inline casts. It’s not pretty for sure. My guess is you had to read the example a few times to realize what it was doing. Last night I was kicking around an extension method to clean this up some. Here’s what I came up with…

public static T CastAs<T>(this object item)
{
  return (T)item;
}

Impressive, eh? Now the original example can be rewritten like this…

item.CastAs<Class2>().AProperty.CastAs<Class1>().AMethod()

Still not what I’d call beautiful code, but I bet you could figure out what it was doing the first time you read it, no? However, there’s one down side. In the original example if I had tried to do something like this…

int item;
((Class1)((Class2)item).AProperty().AMethod()

…I would get a nice compiler warning letting me know that “int” can’t be cast as a “Class2″. Using the CastAs extension method, that nice compiler warning turns into a nasty runtime bug. So the question for you, dear reader, is, is CastAs a good idea or a bad idea? (and the second question is, is, “is, is” valid english?)

For me, I actually ended up taking it back out for now. Instead, I rewrote the original example as…

Class2 class2 = (Class2)item;
Class1 class1 = (Class1)class2.AProperty();
class1.AMethod()

What are your thoughts?

  • Share/Save/Bookmark

Jul 13 2009

Creating a Sql Database At Runtime

In creating OpenForum, one of the things I felt was really important was that it be as easy as possible for users to get up and running. The more steps involved in getting it setup, the fewer people who would actually try it out. I know I’ve had more then a few experiences of downloading a development tool, getting 7 steps into the install/setup process, and then deciding to look into some of the tools competitors. I want tools that are simple and powerful. If it’s a pain to just get them running, what is it going to be like to customize them? Another thing I really hate is having to copy copious numbers of files into specific directories. I’ve used at least a few WYSIWYG editors that had 10x more files then the actual application I was building! So, when it came to a database for OpenForum, I wanted to avoid requiring users to do any connection string configuration, script running, or file copying. One utility that I’ve seen which I feel does a great job of solving that problem is the ASP.NET Membership Provider, especially the way that it’s used in the default MVC template. If you’ve never tried it out, it’s pretty slick. Here are the steps to get it installed/configured.

1. Do nothing

Not bad, eh? All you have to do is run the project and go to any of the pages that use membership (i.e. try logging in). As soon as you do anything that requires the use of the Membership Provider module, a database is automagically generated for in the App_Data directory. That’s what I call easy. That was exactly the experience I wanted for OpenForum. Run the application and a database is created for you.

However, it turns out that it’s not as straight forward as I was hoping. The key piece behind making everything work is a feature in SqlExpress called User Instances. User instance databases are created on a per user basis and can be attached to the SqlExpress engine at runtime. Here’s what a connection string looks like

Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\Database1.mdf; Integrated Security=True; User Instance=True

Notice the “AttachDbFilename” property. That’s what tells Sql where the database file is on disk. The “|DataDirectory|” bit will be expanded to the path to the App_Data directory for the current project.

I’m using LinqToSql, so I did a quick experiment using CreateDatabase , but quickly ran into a problem. It turns out there is a bug somewhere in the bowels of LinqToSql that causes the AttachDbFilename stuff to fail if the path to the database file (the .mdf file) is more then 128 characters. So, I broke out the second best development tool there is, and decompiled the ASP.NET Membership Provider code to figure out how the guys at Microsoft were doing it. It turns out the solution is to first create the database in a temp directory and then copy it to where you need it to go. Not too bad really as far as workarounds go. With a few minutes more of fooling around, I had the following code that seems to work really well.


using (OpenForumDataContext context =
          new OpenForumDataContext(DEFAULT_CONNECTION_STRING))
{
    string finalDirectory = (string)AppDomain.CurrentDomain
                                      .GetData("DataDirectory");

    if (!Directory.Exists(finalDirectory))
    {
        Directory.CreateDirectory(finalDirectory);
    }

    string finalPath = Path.Combine(finalDirectory, "OpenForum");

    if (!File.Exists(finalPath + ".mdf"))
    {
        string tempPath = Path.Combine(Path.GetTempPath(),
                         Guid.NewGuid().ToString());

        using (OpenForumDataContext tempContext =
                    new OpenForumDataContext(tempPath + ".mdf"))
        {
            tempContext.CreateDatabase();
            tempContext.ExecuteCommand(Resources.DefaultData);
            tempContext.ExecuteCommand(@"Declare @name as varchar(100);
                          set @name = DB_NAME();
                          Use master;
                          exec sp_detach_db @name, 'true';");
        }

        File.Move(tempPath + ".mdf", finalPath + ".mdf");
        File.Delete(tempPath + ".ldf");
    }
}
  • Share/Save/Bookmark

Jul 5 2009

Why software is hard (Part 1 of 10,000)

The other day one of my coworkers made a really profound statement. It went a little something like this. “All software is crap. The sooner you realize that, the better off you’ll be.” I think I know what he means by that. Almost all software seems slower, harder to use, and buggier then it seems it should be… including the software that I’ve written. The truth is writing good software is really hard.

The other day, while carpooling home from work, my friend and I had an interesting conversation on one of the many aspects of software that make it so difficult. Software, by its very definition, is not flexible. People are. We have a general set of “rules” that govern our day to day decision making. Those rules make the general cases of our decision making pretty straight forward. For example, if the light is red we stop and if it’s green we go. However there are billions of exceptions to that rule. If the light is green, but there’s a semi stopped in the middle of the intersection, we don’t go. Some of these scenarios are pretty straight forward. We’ve run into them thousands of times, and we know how to react. But, what if the light is green and there’s a semi stopped in the middle of the intersection and there’s a herd of charging elephants bearing down behind you? Ok, maybe not likely, but the point is that there are lots of scenarios that come up in every day life that we have no plan for. In fact, we likely wouldn’t ever have a plan until the scenario came up. In essence, we decide how to react when the situation occurs.

In software we call these scenarios “edge cases” and we do our best to code for them. The problem is that many times there isn’t a way to allow users to “decide how to react when the situation occurs”. All those options need to be programmed into the software before it’s released.

I see this a lot in business software. In business, you do what you need to do to make the customer happy (especially if it’s a big enough customer). It’s not uncommon to bend the rules to meet customer’s needs. In fact, it’s not at all uncommon to have the rules change from day to day depending on the customer, the market, and what the sales person had for breakfast that morning. Today there may be zero exceptions to the “Net 30” terms. Tomorrow the biggest account might be told “Net 593” is no problem.

The point is that business (and life in general) is very flexible. We have general rules that we follow, but we also are free to change many of those rules as necessary in order to navigate the complex systems in which we live. Software on the other hand is ridged. Software is true or false. Software does this or it does that. Software is not flexible. We try our best as programmers to make it as flexible as possible, but inevitably at some point a user will need it to work in a way that is contrary to the rules. And what does the user say when that happens? “This software sucks!”

  • Share/Save/Bookmark

Jun 23 2009

How to Keep Your Sanity and Multiple Projects Version Numbers in Sync

NOTE: This is an article that I originally wrote for codeproject.com. You can view the original article here…

http://www.codeproject.com/KB/cs/Sync_Version_Numbers.aspx

Introduction: Versioning Troubles
In the project I’m currently working on, we have multiple projects in one solution. One of the problems we’ve faced is how to keep all the version numbers (generally found in the ApplicationInfo file) in sync. Ok, so “problem” might be too strong of a word. It’s more of an annoyance, but nonetheless, it would be nice to have some sort of automated solution. The other day, I downloaded some sample code for an unrelated issue, but they had a really nice solution to the versioning puzzle. I’ve now added this solution to my current project, with a few little wrinkles, and it seems to be working really well (we won’t know for sure until the next release). [NOTE: this solution assumes you want the same version number for each project in the solution]

The Short of It
Here’s what you need to do:

1.Remove these attributes from the AssemblyInfo files in each project in the solution:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
2.Create a new file in the root directory of the solution (I named mine VersionInfo).
3.Add the attributes that we removed from the AssemblyInfo file to your new file (you’ll also need to add a reference to the System.Reflection namespace).
4.On each project in the solution, right click the project and select Add->Existing Item.
5.HERE’S THE KEY. Browse to your newly created version file, but instead of clicking the “Add” button, click the little down arrow next to the word “Add” and then click “Add as link” from the menu it drops down.

What Did We Just Do?
Normally when you add an existing file, the IDE copies the selected file to the current directory. By selecting “Add as link,” what we’ve done is link the file in from its original location (yes, I know you figured that out from the name “Add as link”). Now when we build our solution, each project will compile in the exact same file (VersionInfo in my case) , thus giving each project the same version number.

The Next Step
To make this really cool (well, at least I think it’s cool) I’ve created a build script to automate the process of creating the release build. The script prompts for the version number of the release and updates the version file before doing the build. Here’s a snippet of my script (parts of this script were omitted to protect the innocent)…

Dim versionNumber
Set shell = CreateObject("WScript.Shell")
Set fileSystemObject = CreateObject("Scripting.FileSystemObject")

sub Main()
Echo "Getting version number"
versionNumber = InputBox("What version is this build?", "Version")
UpdateFileVersion versionNumber

CommitToVersionControl
GetLatestFromVersionControl

Echo "Building"
RunCommand _
"""C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe""_
SolutionName.sln /Rebuild Release", "Failed to build"

RunUnitTests
CreateZipFile

MsgBox("Done.")
end sub

sub Echo(message)
WScript.Echo message
end sub

sub UpdateFileVersion(versionNumber)
Set file = fileSystemObject.OpenTextFile("Version.cs", 2)

file.WriteLine "using System.Reflection"+ vbcrlf + _
vbcrlf + _ "[assembly:AssemblyVersion""" + _
versionNumber + ".0.0"")]" + _
vbcrlf + "[assembly:AssemblyFileVersion""" + _
versionNumber + ".0.0"")]"

file.Close
end sub

sub RunCommand(command, failMessage)
result = shell.Run(command, 1 , 1)
TestResult result, failMessage
end sub

sub TestResult(result, failMessage)
if result <> 0 then
MsgBox(failMessage)
WScript.Quit
end if
end sub

Main

Conclusion
I now have a very simple and very automated versioning process. It makes sure that our projects’ version numbers are never out of sync and, more importantly, makes sure that I don’t forget to update the version numbers when I do a final release build. I hope this helps.

  • Share/Save/Bookmark

May 28 2009

The Golden Boy

A friend of mine recently made a comment to me, and I’ve been thinking about it quite a bit since. He was commenting on how the management at his company was suffering from the “hero syndrome” where any employee that makes some sort of noteworthy accomplishment suddenly becomes the hero of the company. Once someone is the hero, all things go through that employee. Any new ideas are brought to the hero first. Any new interesting work is assigned to the hero. heroBasically, the hero is viewed as the most important employee in the company. We used to have a different name for this at a company I worked at years ago. We called it “the golden boy”. It turns out this is a very common mindset. I can think of more then a few companies that I’ve worked at that have had a similar mentality. Sometimes I’ve been the golden boy. Sometimes… not so much.

My wife pointed out that this attitude runs on a much deeper level then just the workplace. We love movies about the lone hero who is able to accomplish what no one else can. We like our sports team, but love the star player.

The really interesting part to me is that the golden boy torch seems to be passed quite frequently. Today’s hero may be replaced tomorrow when another employee meets a tight deadline or creates an impressive piece of technology. The real problem with this mentality is that it limits a companies potential. Tomorrows golden boy is certainly no less intelligent or less talented today, but by not giving him/her the same opportunities a company is artificially limiting what it can achieve.

My friend’s conclusion was that the real goal should be to cultivate a sense of team, and I think he’s spot on. It’s terribly cliché, but a team will always out perform an individual. The problem with individuals is that no matter how incredible they are, at some point they will fail. Humans are just not capable of perfection. If you ever read through the Old Testament, it’s full of stories about individuals. However, almost every one of them is near fatally flawed. The Jewish people still consider King David as one of their greatest heroes, but he killed a man in order to steal his wife (resulting in all sorts of dysfunction in his family). Multiple ethnic groups consider Abraham to be their patriarch, but he actually gave his wife away to other men on two separate occasions in order to save his own neck. I think the point is that great men are still just that… men. And people are defective at best. 

Teams are not perfect either, but with more then one voice the likely hood of major oversights or completely self serving decisions is put in check. One of the hardest jobs I ever had was where I was the only developer. I agonized over every technical decision because I didn’t have anyone to bounce my thoughts off of. I was always afraid that I was missing something obvious. I think every developer has had the experience of architecting a beautiful solution to some complex problem, only to have a co-worker point out that the same thing could have been done in two lines of code. This is the same beauty in the architecture of the American governing system. Decisions about the direction of the country are made by a team. You can argue how good of a team our government is, but it seems to me that it’s still highly preferable to a monarchy.

So the question then is how do we avoid the trap of making certain individuals into heroes and instead cultivate a sense of team? This is where you come in. I’d love to hear your thoughts. However, I think the first step is to stop the golden boy mentality. It seams to be the antithesis of team.

  • Share/Save/Bookmark

May 17 2009

End “Null Reference” Now

Stop Nulls

Here’s an interesting statistic, 87.35% of exceptions that occur in the wild are “null reference” exceptions. Here’s another interesting stat, 74.32% of all statistics are made up.

Ok, so I have no idea what the actual percentages are, but my experiance is that “null reference” is hands down the most common exception that I run in to. Well, I’d like to see that put to an end. It’s 2009. We’ve put a man on the moon. We can solve this.

Now I’m no expert when it comes to creating programming languages, but I’d love to see the compiler help out with this. Imagine something like this (notice the attribute in the parameter decleration)…

public void MyNullSafeFunction([NonNull]object value)
{
     // put code here with a smile
     // because you know that "value" can't be null.
}

And then later when some idiot (most likely me) tries to code this…

object value = null;
MyNullSafeFunction(value);

…the compiler would give a nice friendly reminder that I can’t pass a null value as the first argument to MyNullSafeFunction. Infact, there’s no reason it couldn’t even be smart enough to handle this…

object value = null;

if (someVariableThatMyOrMayNotEqualTrue)
{
     value = 4;
}

MyNullSafeFunction(value);

Again, the compiler could warn me that the variable “value” cannot be garanteed to be non-null in all cases. But, what about this case…

object value = FunctionFromLibraryIDoNotHaveSourceCodeFor();
MyNullSafeFunction(value);

Sure, the compiler could analyse the function to verify that it couldn’t possibly return null, but that’s just not very efficent. Especially if that functions return value is dependant on the return value from another function, which is dependant on the return value from another function, which is dependant on the return value from another function, which is dependant on the return value from another function, which is dependant on the return value from another function, which is dependant on the return value from another function. This is where things get harder. But, what if FunctionFromLibraryIDoNotHaveSourceCodeFor had a signature like this…

[NonNull]
public object FunctionFromLibraryIDoNotHaveSourceCodeFor()
{
     // really awesome code here...
}

Ah, problem solved. The compiler could once again verify that the function would not return null in an efficent manner.

However, this is where the problems start to come in. This would require the small task of retro fitting years worth of code with [NonNull] attributes. Also, it could easily end up like the c++ “const” keyword (i.e. really helpful… if everyone uses it). If half the development community adopted the practice and the other half didn’t it could end up being a real nightmare.

However, this isn’t reality land. This is blog-ality land where I can happily ignore the messy details. And so, this marks the begining of my offical campaign to end null references errors forever (I’ll leave working out the details as an excercise for the implementors).

  • Share/Save/Bookmark

May 16 2009

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

 

NOTE: This is an article that I originally wrote for codeproject.com. You can view the original article here…

http://www.codeproject.com/KB/database/DynamicMethod_ILGenerator.aspx

 

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

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;
    }
}

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:


public class ReflectionBuilder
{
    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 CreateBuilder
                                           (SqlDataReader reader)
    {
        ReflectionBuilder result = new ReflectionBuilder();

        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;
    }
}

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:


public class DynamicBuilder
{
    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 CreateBuilder
                                         (IDataRecord dataRecord)
    {
        DynamicBuilder dynamicBuilder = new DynamicBuilder();

        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;
    }
}

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:


Person myPerson = 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:
Person myPerson;

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

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))
{
    ...
}

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:
myPerson.Name = (string)mySqlDataReader[1];
The last part of the code returns the value of the local variable:

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

or

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:

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

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:

dynamicmappingspike image

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.

  • Share/Save/Bookmark