2014-05-07 40 views
2

我創建並使用下面的代碼緩存屬性訪問代表創建代表:緩存屬性存取

static Delegate CreateGetterDelegate<T>(PropertyInfo propertyInfo) 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType); 
     var property = Expression.Property(instance, propertyInfo); 
     var convert = Expression.TypeAs(property, typeof(object)); 
     return (Func<T, object>)Expression.Lambda(convert, instance).Compile(); 
    } 

這是工作,並且運行良好(謝謝你的StackOverflow!),但是我想去除通過返回T和對象的Func所需的裝箱/拆箱。有沒有什麼辦法可以改變返回值,以便返回類型是T和typeofproperty的Func?

static Delegate CreateGetterDelegate<T>(PropertyInfo propertyInfo) 
    { 
     if (typeof(T) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     ... some magic happening here ... 

     return (Func<T, typeofproperty>)Expression.Lambda(...more magic...).Compile(); 
    } 

注意 - 我使用VS2013和.NET 4.5

+0

這是如何拳擊/拆箱?你只是施放了一個ref類型.. –

+0

@ TMcKeown,如果'T'是一個'int',那就是裝箱了。 –

+0

好的,gotcha。你必須能夠改變'代表' –

回答

5

做到這一點的唯一途徑是屬性的類型在編譯時是已知的,這兩種方法定義之內,或呼叫者。如果此方法的調用者知道該屬性的類型是什麼,那麼就可以使用第二通用參數指定:

static Func<TInstance, TResult> CreateGetterDelegate<TInstance, TResult>(
    PropertyInfo propertyInfo) 
{ 
    if (typeof(TInstance) != propertyInfo.DeclaringType) 
    { 
     throw new ArgumentException(); 
    } 

    var instance = Expression.Parameter(propertyInfo.DeclaringType); 
    var property = Expression.Property(instance, propertyInfo); 
    var convert = Expression.TypeAs(property, typeof(object)); 
    return (Func<TInstance, TResult>)Expression.Lambda(convert, instance) 
     .Compile(); 
} 

當然,這只是推動問題返回給調用者。如果連主叫方都不知道房產的類型是什麼,那麼他們也有同樣的問題。

在避免拳擊的一天結束時,某些代碼需要在編譯時知道該屬性的類型是什麼。如果某個人在某個地方會在編譯時知道,那麼您可以繼續使用泛型來解決問題,直到您達到可以使用實際已知類型的那個點。如果沒有人知道編譯時的類型,那麼這個問題真的沒有任何解決方案。

如果你想獲得技術,可以通過使事情變得比他們更加動態來避免拳擊(使用反射來調用這些使用反射來做東西的方法),但是從技術角度來看, ,能夠避免字面box命令,你會失去超出你的收益。這不是一個實際的解決方案。

+0

+1;我相信你也可以做'Expression.Lambda '對嗎?但我想你仍然必須將它轉換爲'Func '呵呵? –

+2

@MichaelPerrenoud無論您是將lambda的委託類型作爲其泛型參數提供,還是在編譯它之後進行強制轉換都不相關。他們都做同樣的事情,並在大約相同數量的代碼。 – Servy

0

以上約再次使用反射的評論讓我思考(總是危險的前景),我已經想出了以下解決方案,基本上實現Servy的建議:

static Delegate CreateGenericGetterDelegate<TClass>(PropertyInfo propertyInfo) 
    { 
     var method = typeof(Cache).GetMethod("CreateTypedGetterDelegate"); 
     MethodInfo generic = method.MakeGenericMethod(new Type[] { typeof(TClass), propertyInfo.PropertyType }); 
     return (Delegate) generic.Invoke(new object(), new object[] { propertyInfo }); 
    } 

    public static Delegate CreateTypedGetterDelegate<TClass, TProp>(PropertyInfo propertyInfo) 
    { 
     if (typeof(TClass) != propertyInfo.DeclaringType) 
     { 
      throw new ArgumentException(); 
     } 

     var instance = Expression.Parameter(propertyInfo.DeclaringType); 
     var property = Expression.Property(instance, propertyInfo); 
     if (typeof(TProp).IsValueType) 
     { 
      return (Func<TClass, TProp>)Expression.Lambda(property, instance).Compile(); 
     } 
     else 
     { 
      return (Func<TClass, TProp>)Expression.Lambda(Expression.TypeAs(property, typeof(TProp)), instance).Compile(); 
     } 
    } 

這工作,提供類型 - 安全的訪問和避免任何明顯的拳擊/拆箱。由於這在我的程序開始時發生過一次(並且只對於實際訪問的屬性),因此對於用於屬性訪問器的類型化代表的返回,我可以確定反射的性能。

+0

此方法不起任何作用。給定的委託仍然需要轉換爲實際的委託類型以便調用它,這需要在編譯時知道該屬性的類型。要麼,要麼使用'DynamicInvoke',這仍然會打結果。你的'CreateGenericGetterDelegate'方法在OP的實現中增加了零值,因爲你仍然返回一個'Delegate',而不是派生出來的東西。 – Servy

+0

嗯...所以,基本上你說的是雖然這是*技術上正確*(即,它的工作原理),我真的在做很多工作,沒有真正的收益,因爲返回一個委託和在結果上使用DynamicInvoke?所以,至少我學到了一些東西,即使我做了相當於舊式的練習,每個人都在紅綠燈處下車並在車上跑來跑去,試圖在燈變綠之前再回到車上...... –

+0

最後,一些令人不可思議的日子。我在OP中調用原始「CreateGetterDelegate」並在此答案中使用了通用化的「CreateGenericGetterDelegate」。我還通過DynamicInvoke調用返回的代理做了一些時間安排。超過100000次迭代,OP方法平均花費了11602毫秒,更新後的通用版花費了10194毫秒。雖然這些都很不錯(每次調用超過1毫秒!)有趣的是,「通用」版本總是更快。 –