Friday, April 20, 2012

Reflection.Emit: Heaven, but Hell with DateTime

We had to write a framework for a client, instant alarm bells, when I hear framework. Anyway they wanted to port an old style classic ASP app that was table driven, ie you add a table name to a setup screen and viola you have a screen to do CRUD on the data of that table etc etc.

This had to be ported into SharePoint and we decided to use EF4, WCF and Silverlight (no im not using an splist and bcs). Problem 1 is that WCF needs a defined contract and we can’t just pass “T” up and down. So we needed some code generation to generate the types at runtime, based on the table. Fairly easy, get the schema from the data returned and generate the type using CodeDom, been done before. Problem solved.

Problem 2: CodeDom cannot be run under Silverlight! Eish! But we can use Reflection.Emit. So I did and it worked after much learning and failure. Understanding IL is rather intense for an old coder like me (old dogs new tricks).

Im not going to go through the whole tutorial, but rather, just mention a problem we had that I could not find an answer on the WWW. When generating the code and runtime, it looked fine when you created and reflected it, but it doesn’t execute, infact you get a lovely {"Exception has been thrown by the target of an invocation."} error.

This is the code I needed to generate:

private int _EmployeeID;

private DateTime _StartDate;

public int EmployeeID

{

get

{

return this._EmployeeID;

}

set

{

if (value != this._EmployeeID)

{

this._EmployeeID = value;

this.OnPropertyChanged("EmployeeID");

}

}

}

public DateTime StartDate

{

get

{

return this._StartDate;

}

set

{

if (!(value!= this._StartDate))

{

this._StartDate = value;

this.OnPropertyChanged("StartDate");

}

}

}

So this Reflecton.Emit code below generates the If(value!=this.x) bit in the setter:

generator.DeclareLocal(typeof(bool));

generator.Emit(OpCodes.Nop);

generator.Emit(OpCodes.Ldarg_1);

generator.Emit(OpCodes.Ldarg_0);

generator.Emit(OpCodes.Ldfld, field);

generator.Emit(OpCodes.Ceq);

generator.Emit(OpCodes.Brtrue_S, endif);

The generated code looks sweet but if you run it, it fails on assigning the DataTime property, with that Invocation error. When I removed the “IF” generation block all was fine

Using ILSpy and Reflector, I cut and pasted the generated code in a separate project dll and compared the IL of this code vs. my generated type and they were different!

When generating a DateTime the compiled IL looked like this:

IL_0000: nop

IL_0001: ldarg.1

IL_0002: ldarg.0

IL_0003: ldfld valuetype [mscorlib]System.DateTime WpfApplication1.Employee::_StartDate

IL_0008: call bool [mscorlib]System.DateTime::op_Equality(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTime)

IL_000d: stloc.0

IL_000e: ldloc.0

IL_000f: brtrue.s IL_0026

My generated class looked like this:

IL_0000: nop

IL_0001: ldarg.1

IL_0002: ldarg.0

IL_0003: ldfld valuetype [mscorlib]System.DateTime SharePoint.SilverLight.Web.Employee::_StartDate

IL_0008: ceq

IL_000a: brtrue.s IL_0021

So looking at the compiled IL, is seems .Net is doing a DateTime.Equals(A,B) and not an if(A==B)

So for DateTime’s I changed my code to this.

generator.DeclareLocal(typeof(bool));

generator.Emit(OpCodes.Nop);

generator.Emit(OpCodes.Ldarg_1);

generator.Emit(OpCodes.Ldarg_0);

generator.Emit(OpCodes.Ldfld, field);

if(type==typeof(DateTime))

{

MethodInfo dateCompareMethod = typeof(DateTime).GetMethod("Equals",

new Type[] {

typeof(DateTime), typeof(DateTime)

}

);

generator.Emit(OpCodes.Call, dateCompareMethod);

generator.Emit(OpCodes.Stloc_0);

generator.Emit(OpCodes.Ldloc_0);

}

else

{

generator.Emit(OpCodes.Ceq);

}

generator.Emit(OpCodes.Brtrue_S, endif);

Viola code worked J and IL code looks like it should!

Hope this helps someone else.

No comments:

Post a Comment