天天看點

玩轉動态編譯 - 進階篇:三,執行個體屬性的讀取與設定

玩轉動态編譯 - 進階篇:三,執行個體屬性/字段的讀取與設定

  • 執行個體屬性的讀取

先來回顧下靜态屬性讀取的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;
}      

差別很明顯,多個一個指令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));
    }
}      

現在就可以寫出一個将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;
}      
玩轉動态編譯 - 進階篇:三,執行個體屬性的讀取與設定
  • 反射和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倍左右的性能差異

我寫的文章,除了純代碼,其他的都是想表達一種思想,一種解決方案.希望各位看官不要局限于文章中的現成的代碼,要多關注整個文章的主題思路,謝謝!

我釋出的代碼,沒有任何版權,遵守WTFPL協定(如有引用,請遵守被引用代碼的協定)

qq群:5946699 希望各位喜愛C#的朋友可以在這裡交流學習,分享程式設計的心得和快樂