前言
標題實在是太長了,是嗎?其實並無什麼高深的內容。今天看了 Will Meng 的 《Activator.CreateInstance(Type type)方法建立對象和Expression Tree建立對象效能的比較(終結版)》一文,請沒有看過該博文的朋友先前往圍觀,或許會得到一些啟發。而我想知道如果建立對象帶參數會是什麼樣的情況呢,可惜沒有看到,於是自己動手試試,作為前文的一點補充。而本文另一個目的是,對於多參數的情況,本人提供的實現方法實在是不雅,請高手指點如何重構它們成為一個通用的版本。
Expression Tree 建立對象1. 無參數的情況
public static Func<object> CreateInstanceDelegate(this Type type){ NewExpression newExp = Expression.New(type); Expression<Func<object>> lambdaExp = Expression.Lambda<Func<object>>(newExp, null); Func<object> func = lambdaExp.Compile(); return func;}
(註:以上方法是擴充方法,請放到一個static class裡)
測試:
Type type = typeof(Bar);var createInstance = type.CreateInstanceDelegate();for (int i = 0; i < 1000000; i++){ createInstance();}
好的,如果你看明白了上面的代碼,那麼接著看下去才有意義。
2. 多參數情況
我試圖一步到位建立以下通用的含多參數的建立對象方法:
Func<object[], object> CreateInstanceDelegate(Type type, params object[] args)
結果多次修改嘗試仍然失敗,雖然我知道失敗的原因,但不知道如何修正它。
//以下代碼有bug!public static Func<object[], object> CreateInstanceDelegate(this Type type, params object[] args){ var construtor = type.GetConstructor(args.Select(c => c.GetType()).ToArray()); var param = buildParameters(args); NewExpression newExp = Expression.New(construtor, param); Expression<Func<object[], object>> lambdaExp = Expression.Lambda<Func<object[], object>>(newExp, param); Func<object[], object> func = lambdaExp.Compile(); return func;}static ParameterExpression[] buildParameters(object[] args){ int i = 0; List<ParameterExpression> list = new List<ParameterExpression>(); foreach (object arg in args) { list.Add(Expression.Parameter(arg.GetType(), "arg" + (i++))); } return list.ToArray();}
(註:再次說明以上代碼有bug,不能使用)
以上方法中的Lambda運算式“Expression<Func<object[], object>> ”已經定義參數是object[], 而建構函式的參數卻不能自動轉化。當使用以下代碼作測試,
var createInstance = typeof(Bar).CreateInstanceDelegate(0, "");
結果報“Incorrect number of parameters supplied for lambda declaration” 錯誤。或許需要作一個運算式Convertor 就可以,不過暫時沒進一步探究。
無奈之下,我唯有先搞定一個參數的情況,
public static Func<T, object> CreateInstanceDelegate<T>(this Type type){ Type paramType = typeof(T); var construtor = type.GetConstructor(new Type[] { paramType }); var param = new ParameterExpression[] { Expression.Parameter(paramType, "arg") }; NewExpression newExp = Expression.New(construtor, param); Expression<Func<T, object>> lambdaExp = Expression.Lambda<Func<T, object>>(newExp, param); Func<T, object> func = lambdaExp.Compile(); return func;}
通過使用泛型,建立Lambda運算式時不會報參數不匹配的錯誤了。
這裡定義一個Bar類型作測試用,有3個建構函式:
public class Bar{ public Bar() { } public Bar(int num) { } public Bar(int num, string str) { }}
測試例子:
Type type = typeof(Bar);var createInstance = type.CreateInstanceDelegate<int>();for (int i = 0; i < count; i++){ createInstance(i);}
這裡列出 Activator.CreateInstance,Expression Tree 和直接使用 new 的效能測試代碼:
static void Test2(){ Console.WriteLine("Test2 - CreateInstance(帶1參數): "); Stopwatch watcher = new Stopwatch(); Type type = typeof(Bar); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { Activator.CreateInstance(type, i); } watcher.Stop(); Console.WriteLine("Activator: " + watcher.Elapsed); watcher.Reset(); watcher.Start(); var createInstance = type.CreateInstanceDelegate<int>(); for (int i = 0; i < count; i++) { createInstance(i); } watcher.Stop(); Console.WriteLine("Expression: " + watcher.Elapsed); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { new Bar(i); } watcher.Stop(); Console.WriteLine("Direct: " + watcher.Elapsed); Console.WriteLine();}
測試通過,而且效能也優於Activator.CreateInstance,詳細結果請看文章末的總體測試結果。
問題總結:
實際開發中,我希望得到的版本是跟Activator.CreateInstance 一樣的:
public static object CreateInstance(Type type, params object[] args);
那麼,得到問題一:如何來封裝以下方法
Func<T, object> CreateInstanceDelegate<T>(this Type type)
如何緩衝這種委託 Func<T, object> ?
使用Expression Tree建立含兩個參數的建構函式,雖然也是照葫蘆畫瓢的事情,如
public static Func<T1, T2, object> CreateInstanceDelegate<T1, T2>(this Type type){ var types = new Type[] { typeof(T1), typeof(T2) }; var construtor = type.GetConstructor(types); int i = 0; var param = types.Select(t => Expression.Parameter(t, "arg" + (i++))).ToArray(); NewExpression newExp = Expression.New(construtor, param); Expression<Func<T1, T2, object>> lambdaExp = Expression.Lambda<Func<T1, T2, object>>(newExp, param); Func<T1, T2, object> func = lambdaExp.Compile(); return func;}
但是這是個好方法嗎?我想不是,那麼得到問題二: 對於任意多個參數應該怎麼寫才合理?
代碼下載:CoolCode.Linq.Expressions.rar總體測試結果:
測試次數皆是一百萬次
Test1 - CreateInstance(無參數):Activator: 00:00:00.1673892Expression: 00:00:00.0126696Direct: 00:00:00.0067769Test2 - CreateInstance(帶1參數):Activator: 00:00:02.9516177Expression: 00:00:00.0135498Direct: 00:00:00.0074452Test3 - CreateInstance(帶2參數):Activator: 00:00:03.1932820Expression: 00:00:00.0151513Direct: 00:00:00.0063228
以上結果顯示 Activator.CreateInstance 如果帶多個參數,速度明顯下降,而後兩者則影響不大。