玩转动态编译

  • 实例属性的读取

先来回顾下静态属性读取的IL代码:

.method public hidebysig instance string AAA() cil managed
{
    .maxstack 8
    L_0000: call string blqw.IL.Demo.Program/MyClass::get_Name()
    L_0005: ret 
} 
string AAA()
{
    return MyClass.Name;
}
C#代码

再来看下读取实例属性的IL代码

.method private hidebysig instance string AAA(class blqw.IL.Demo.Program/MyClass my) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: callvirt instance string blqw.IL.Demo.Program/MyClass::get_Name()
    L_0006: ret 
}
string AAA(MyClass my)
{
    return my.Name;
}
C#代码

区别很明显,多个一个指令ldarg.0 ,并且指令有所区别

//     将索引为 0 的参数加载到计算堆栈上。
public static readonly OpCode Ldarg_0;

操作实例方法和操作静态方法不同,静态方法不需要任何额外的参数,而实例方法必须要提供一个参数,这个参数指示操作的实例对象

转换成C#代码就是这样的

public static Func<MyClass, string> ILTest()
{
    var type = typeof(MyClass);
    var prop = type.GetProperty("Name");//反射属性

    var dm = new DynamicMethod("", typeof(string), new[] { typeof(MyClass) }, type);
    var il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Callvirt, prop.GetGetMethod());
    il.Emit(OpCodes.Ret);
    return (Func<MyClass, string>)dm.CreateDelegate(typeof(Func<MyClass, string>));
}

  • 实例属性的设置

IL代码

.method private hidebysig instance void AAA(class blqw.IL.Demo.Program/MyClass my, string name) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldarg.1 
    L_0002: callvirt instance void blqw.IL.Demo.Program/MyClass::set_Name(string)
    L_0007: ret 
}

好吧,又多了一个参数,不过这个是显而易见的,既然你要设置值,总要把值当作参数传递进去吧

对应C#代码如下:

public static Action<MyClass, string> ILTest()
{
    var type = typeof(MyClass);
    var prop = type.GetProperty("Name");//反射属性

    var dm = new DynamicMethod("", null, new[] { typeof(MyClass), typeof(string) }, type);
    var il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Callvirt, prop.GetSetMethod());
    il.Emit(OpCodes.Ret);
    return (Action<MyClass, string>)dm.CreateDelegate(typeof(Action<MyClass, string>));
}

重复来重复去都是这几样东西了..有些无聊了吧...

 那么接下来就做一些实际情况下的应用了

  • 实际应用

这次我要举起来的栗子就是DataTable转模型对象

不过在这之前,我要对现有的方法进行一些调整

public delegate void PropertySetter(object instance, object value);
public static PropertySetter CreateSetter(PropertyInfo property)
{
    var type = property.DeclaringType;
    var dm = new DynamicMethod("", null, new[] { typeof(object), typeof(object) }, type);
    //=== IL ===
    var il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    if (property.PropertyType.IsValueType)//判断属性类型是否是值类型
    {
        il.Emit(OpCodes.Unbox,property.PropertyType);//如果是值类型就拆箱
    }
    else
    {
        il.Emit(OpCodes.Castclass, property.PropertyType);//否则强转
    }
    il.Emit(OpCodes.Callvirt, property.GetSetMethod());
    il.Emit(OpCodes.Ret);
    //=== IL ===
    return (PropertySetter)dm.CreateDelegate(typeof(PropertySetter));
}

修改的地方不是很多,应该不难理解,关于类型转换部分,请参考上一篇

现在我可以很方便的通过这个方法创建一个任意实例属性的Set方法委托

接下来我需要一个的新的类

public class ObjectProperty
{
    public PropertyInfo Info { get; set; }
    public PropertySetter Setter { get; set; }
}

这个类包含一个属性和这个属性的Set方法委托

在接下来我需要一个方法,把任意一个类中的所有公开的实例属性,转换成ObjectProperty集合

static readonly Dictionary<Type, ObjectProperty[]> Cache = new Dictionary<Type, ObjectProperty[]>();

public static ObjectProperty[] GetProperties(Type type)
{
    ObjectProperty[] arr;
    if (Cache.TryGetValue(type, out arr))//优先从缓存中获取
    {
        return arr;
    }
    PropertyInfo[] ps = type.GetProperties(); 
    arr = new ObjectProperty[ps.Length];
    for (int i = 0; i < ps.Length; i++)
    {
        ObjectProperty op = new ObjectProperty();
        op.Info = ps[i];
        op.Setter = CreateSetter(op.Info);  //之前定义的方法
        arr[i] = op;
    }
    Cache.Add(type, arr); //加入缓存
    return arr;
}

 把他们整合起来

public class ObjectProperty
{
    /// <summary> 属性信息
    /// </summary>
    public PropertyInfo Info { get; set; }
    /// <summary> Set方法委托
    /// </summary>
    public PropertySetter Setter { get; set; }
    //缓存
    static readonly Dictionary<Type, ObjectProperty[]> Cache = new Dictionary<Type, ObjectProperty[]>();

    /// <summary> 获取一个类中的所有公开实例属性和它们的Set方法委托
    /// </summary>
    public static ObjectProperty[] GetProperties(Type type)
    {
        ObjectProperty[] arr;
        if (Cache.TryGetValue(type, out arr))//优先从缓存中获取
        {
            return arr;
        }
        PropertyInfo[] ps = type.GetProperties();
        arr = new ObjectProperty[ps.Length];
        for (int i = 0; i < ps.Length; i++)
        {
            ObjectProperty op = new ObjectProperty();
            op.Info = ps[i];
            op.Setter = CreateSetter(op.Info);  //之前定义的方法
            arr[i] = op;
        }
        Cache.Add(type, arr); //加入缓存
        return arr;
    }
    /// <summary> 创建指定属性的Set方法委托
    /// </summary>
    public static PropertySetter CreateSetter(PropertyInfo property)
    {
        var type = property.DeclaringType;
        var dm = new DynamicMethod("", null, new[] { typeof(object), typeof(object) }, type);
        //=== IL ===
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        if (property.PropertyType.IsValueType)//判断属性类型是否是值类型
        {
            il.Emit(OpCodes.Unbox, property.PropertyType);//如果是值类型就拆箱
        }
        else
        {
            il.Emit(OpCodes.Castclass, property.PropertyType);//否则强转
        }
        il.Emit(OpCodes.Callvirt, property.GetSetMethod());
        il.Emit(OpCodes.Ret);
        //=== IL ===
        return (PropertySetter)dm.CreateDelegate(typeof(PropertySetter));
    }
}
C#代码

现在就可以写出一个将DataTable转为实体类的方法了

public static List<T> ConvertToModels<T>(DataSet ds)
    where T : new()
{
    var prop = ObjectProperty.GetProperties(typeof(T));
    List<T> list = new List<T>(ds.Tables[0].Rows.Count);
    var cols = ds.Tables[0].Columns;
    foreach (DataRow row in ds.Tables[0].Rows)
    {
        T m = new T();
        foreach (var p in prop)
        {
            if (cols.Contains(p.Info.Name))
            {
                var val = row[p.Info.Name];
                if ((val is DBNull) == false)
                {
                    p.Setter(m, val);
                }
            }
        }
        list.Add(m);
    }
    return list;
}

好了,我现在模拟出一个DataSet和一个实体类来测试下

public class User
{
    public User()
    {

    }
    public int Id { get; set; }
    public string Name { get; set; }
    public bool Sex { get; set; }
    public Guid Uid { get; set; }
    public DateTime Time { get; set; }
    public string SexText
    {
        get
        {
            return Sex ? "" : "";
        }
        set
        {
            Sex = (value == "");
        }
    }
}
//模拟方法
static public DataSet GetDataSet(string sql)
{
    DataTable table = new DataTable("User");
    table.Columns.Add("Id", typeof(int));
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("Sex", typeof(bool));
    table.Columns.Add("Uid", typeof(Guid));
    table.Columns.Add("Time", typeof(DateTime));
    table.Columns.Add("多出来的属性", typeof(string));
    for (int i = 0; i < 20; i++)
    {
        table.Rows.Add(i, "blqw" + i, true, Guid.NewGuid(), DateTime.Now, "多余的");
    }

    DataSet ds = new DataSet();
    ds.Tables.Add(table);
    return ds;
}
C#代码

  • 反射和IL

就功能上来说IL可以做的,反射都可以做.基本上IL的操作指令很多参数都是需要用到反射对象的

那么我们为什么要选择麻烦的IL,而不直接用反射呢,答案就是性能

就拿上面的栗子来说,如果我们用反射来实现的话是这样的

static public List<T> ConvertToModels2<T>(DataSet ds)
    where T : new()
{
    var prop = ObjectProperty.GetProperties(typeof(T));
    List<T> list = new List<T>(ds.Tables[0].Rows.Count);
    var cols = ds.Tables[0].Columns;
    foreach (DataRow row in ds.Tables[0].Rows)
    {
        T m = new T();
        foreach (var p in prop)
        {
            if (cols.Contains(p.Info.Name))
            {
                var val = row[p.Info.Name];
                if (Convert.IsDBNull(val) == false)
                {
                    p.Info.SetValue(m, val, null);//这里直接用反射的SetValue
                }
            }
        }
        list.Add(m);
    }
    return list;
}
反射代码

这只是构造10000个只有5个属性的实体类而已

因为先运行的是动态编译IL的测试,所以缓存什么的都已经在这个时候建好了,下面反射只是调用缓存

如果这个测试还看不出太大区别的话,那就看下直接对比Set部分的性能

一般来说是6倍左右的性能差异

原文地址:https://www.cnblogs.com/blqw/p/3261240.html