Skip to content

Instantly share code, notes, and snippets.

@bretcope
Last active March 12, 2019 05:33
Show Gist options
  • Save bretcope/a9dea0c5c67b0bc051c8 to your computer and use it in GitHub Desktop.
Save bretcope/a9dea0c5c67b0bc051c8 to your computer and use it in GitHub Desktop.
Sigil Object Mapping - Basic Examples
// 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; }
}
// 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