Last active
March 12, 2019 05:33
-
-
Save bretcope/a9dea0c5c67b0bc051c8 to your computer and use it in GitHub Desktop.
Sigil Object Mapping - Basic Examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Runnable from Linqpad 5 | |
// Must install the Sigil nuget package and include the Sigil namespace | |
void Main() | |
{ | |
var createSample = GetMapperDelegate(); | |
// try it out | |
createSample(23, "Hello").Dump(); | |
} | |
static Func<int, string, Sample> GetMapperDelegate() | |
{ | |
// going to create a dynamic function which takes an int and string and assigns them | |
// to the properties Id and Name of a new Sample object. | |
// create emitter for the function | |
var emit = Emit<Func<int, string, Sample>>.NewDynamicMethod("NewSample"); | |
// create a new Sample object and save it to a local variable | |
var sample = emit.DeclareLocal<Sample>(); | |
emit.NewObject<Sample>(); // [newobj] | |
emit.StoreLocal(sample); // empty | |
// assign Id | |
emit.LoadLocal(sample); // [sample] | |
emit.LoadArgument(0); // [sample] [id] | |
emit.CallVirtual(typeof(Sample).GetProperty("Id").SetMethod); // empty | |
// assign Name | |
emit.LoadLocal(sample); // [sample] | |
emit.LoadArgument(1); // [sample] [name] | |
emit.CallVirtual(typeof(Sample).GetProperty("Name").SetMethod); // empty | |
// return the object | |
emit.LoadLocal(sample); // [sample] | |
emit.Return(); | |
// we're done emitting IL, actually generate the function now | |
return emit.CreateDelegate(); | |
// Note, we didn't really need to create a local variable, we could have used Duplicate() | |
// to duplicate the top stack item every time we needed to use it before the return statemet. | |
// The example below will use this methodology. | |
} | |
public class Sample | |
{ | |
public int Id { get; set; } | |
public string Name { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Runnable from Linqpad 5 | |
// Must install the Sigil nuget package and include the Sigil namespace | |
void Main() | |
{ | |
var createSample = GetMapperDelegate(); | |
// try it out | |
createSample(new Dictionary<string, string>() | |
{ | |
["One"] = "1", | |
["Two"] = "22", | |
["Three"] = "333", | |
["Four"] = "4444", | |
}).Dump(); | |
createSample(new Dictionary<string, string>() | |
{ | |
["One"] = "Hello", | |
["Three"] = "I didn't set every property.", | |
}).Dump(); | |
} | |
static Func<Dictionary<string, string>, Sample> GetMapperDelegate() | |
{ | |
// going to create a dynamic function which takes a Dictionary<string, string> and | |
// for any string properties of Sample where there is a cooresponding key in the | |
// dictionary, we'll assign the dictionary value to the Sample property value. | |
// create emitter for the function | |
var emit = Emit<Func<Dictionary<string, string>, Sample>>.NewDynamicMethod("NewSample"); | |
const int DICTIONARY = 0; | |
// get all of the public instance properties of sample | |
var props = typeof(Sample).GetProperties(BindingFlags.Public|BindingFlags.Instance); | |
// create a new Sample object | |
emit.NewObject<Sample>(); // [sample] | |
// use reflection to get the Dictionary<string, string>.TryGetValue method | |
var paramTypes = new[] { typeof(string), typeof(string).MakeByRefType() }; // only necessary if there are overloads, but still good practice | |
var tryGetValue = typeof(Dictionary<string, string>).GetMethod("TryGetValue", paramTypes); | |
// declare local which we'll use for getting the output from TryGetValue | |
var value = emit.DeclareLocal<string>(); | |
// for each property which is a string and has a setter, create code which looks for | |
// that value in the dictionary. | |
foreach (var prop in props) | |
{ | |
if (prop.PropertyType != typeof(string) || prop.SetMethod == null) | |
continue; | |
// check the dictionary for a key matching the property name | |
emit.LoadArgument(DICTIONARY); // [sample] [dict] | |
emit.LoadConstant(prop.Name); // [sample] [dict] [propName] | |
emit.LoadLocalAddress(value); // [sample] [dict] [propName] [ref value] | |
// TryGetValue has signature "bool (this Dictionary, string, ref string)" so it consumes three values off the stack and pushes one | |
emit.CallVirtual(tryGetValue); // [sample] [bool valueExists] | |
// declare a label which will later mark the end of our if block | |
var endIfLabel = emit.DefineLabel(); | |
// branch to the end of the if block if "valueExists" is false - consumes one stack value | |
emit.BranchIfFalse(endIfLabel); // [sample] | |
// inside the if block - now we can assign the found value to its corresponding property | |
emit.Duplicate(); // [sample] [sample] | |
emit.LoadLocal(value); // [sample] [sample] [value] | |
// setter has signature "void (this Sample, string)" so it consumes two values off the stack and pushes nothing | |
emit.CallVirtual(prop.SetMethod); // [sample] | |
// mark the end of our if block | |
emit.MarkLabel(endIfLabel); | |
} | |
// we're done assigning properties, now we just return the object (which is the only thing left on the stack) | |
emit.Return(); | |
return emit.CreateDelegate(); | |
} | |
public class Sample | |
{ | |
public string One { get; set; } | |
public string Two { get; set; } | |
public string Three { get; set; } | |
public string Four { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some relevant discussion on http://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/#comment-2521511736