Created
October 20, 2016 15:26
-
-
Save StachuDotNet/83aeee393d54e1ca837d2fa95b2d6d11 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* | |
Add these: | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
*/ | |
void Main() | |
{ | |
WriteDeltas(new Foo { X = 123, Y = DateTime.Today, Z = null }, | |
new Foo { X = 124, Y = DateTime.Today, Z = null }); | |
WriteDeltas(new Foo { X = 123, Y = DateTime.Today, Z = null }, | |
new Foo { X = 123, Y = DateTime.Now, Z = new Dummy() }); | |
} | |
static void WriteDeltas<T>(T x, T y) | |
{ | |
Console.WriteLine("----"); | |
foreach (string delta in PropertyComparer<T>.GetDeltas(x, y)) | |
Console.WriteLine(delta); | |
} | |
class Dummy { } | |
class Foo | |
{ | |
public int X { get; set; } | |
public DateTime Y { get; set; } | |
public Dummy Z { get; set; } | |
} | |
public static class PropertyComparer<T> | |
{ | |
private static readonly Func<T, T, List<string>> getDeltas; | |
static PropertyComparer() | |
{ | |
var dyn = new DynamicMethod(":getDeltas", typeof(List<string>), new[] { typeof(T), typeof(T) }, typeof(T)); | |
var il = dyn.GetILGenerator(); | |
// create a new list of strings | |
il.Emit(OpCodes.Newobj, typeof(List<string>) | |
.GetConstructor(Type.EmptyTypes)); | |
// access the 'Add' method of the generic List<> | |
var add = typeof(List<string>).GetMethod("Add"); | |
foreach (var prop in typeof(T).GetProperties()) | |
{ | |
if (!prop.CanRead) continue; | |
Label next = il.DefineLabel(); | |
switch (Type.GetTypeCode(prop.PropertyType)) | |
{ | |
case TypeCode.Boolean: | |
case TypeCode.Byte: | |
case TypeCode.Char: | |
case TypeCode.Double: | |
case TypeCode.Int16: | |
case TypeCode.Int32: | |
case TypeCode.Int64: | |
case TypeCode.SByte: | |
case TypeCode.Single: | |
case TypeCode.UInt16: | |
case TypeCode.UInt32: | |
case TypeCode.UInt64: | |
// generates something like 'return x.prop == y.prop;' | |
il.Emit(OpCodes.Ldarg_0); // x | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.Emit(OpCodes.Ldarg_1); // y | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.Emit(OpCodes.Ceq); // == | |
break; | |
default: | |
var pp = new Type[] { prop.PropertyType, prop.PropertyType }; | |
var eq = prop.PropertyType.GetMethod("op_Equality", BindingFlags.Public | BindingFlags.Static, null, pp, null); | |
// Try to get the '==' comparater | |
if (eq != null) | |
{ | |
il.Emit(OpCodes.Ldarg_0); // x | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.Emit(OpCodes.Ldarg_1); // y | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.EmitCall(OpCodes.Call, eq, null); // == | |
} | |
else | |
{ | |
il.EmitCall(OpCodes.Call, | |
typeof(EqualityComparer<>) | |
.MakeGenericType(prop.PropertyType) | |
.GetProperty("Default") | |
.GetGetMethod(), | |
null); // .Equals() | |
il.Emit(OpCodes.Ldarg_0); // x | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.Emit(OpCodes.Ldarg_1); // y | |
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // .prop | |
il.EmitCall(OpCodes.Callvirt, typeof(EqualityComparer<>).MakeGenericType(prop.PropertyType).GetMethod("Equals", pp), null); | |
} | |
break; | |
} | |
// add the result to the List<string> | |
il.Emit(OpCodes.Brtrue_S, next); | |
il.Emit(OpCodes.Dup); | |
il.Emit(OpCodes.Ldstr, prop.Name); | |
il.EmitCall(OpCodes.Callvirt, add, null); | |
il.MarkLabel(next); | |
} | |
il.Emit(OpCodes.Ret); // return | |
getDeltas = (Func<T, T, List<string>>)dyn.CreateDelegate(typeof(Func<T, T, List<string>>)); | |
} | |
public static List<string> GetDeltas(T x, T y) { return getDeltas(x, y); } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment