2010-04-09 71 views
3

我正在研究只適用於引用類型的擴展方法。不過,我認爲目前它正在裝箱和拆箱。我怎樣才能避免這種情況?如何在擴展System.Object時避免裝箱/取消裝箱?

namespace System 
{ 
    public static class SystemExtensions 
    { 
     public static TResult GetOrDefaultIfNull<T, TResult>(this T obj, Func<T, TResult> getValue, TResult defaultValue) 
     { 
      if (obj == null) 
       return defaultValue; 
      return getValue(obj); 
     } 
    } 
} 

用法示例:

public class Foo 
{ 
    public int Bar { get; set; } 
} 

在一些方法:

Foo aFooObject = new Foo { Bar = 1 }; 
Foo nullReference = null; 

Console.WriteLine(aFooObject.GetOrDefaultIfNull((o) => o.Bar, 0)); // results: 1 
Console.WriteLine(nullReference.GetOrDefaultIfNull((o) => o.Bar, 0)); // results: 0 

回答

4

這不是拳擊。你認爲它在哪裏拳擊?如果是因爲你已經在「==」周圍看了IL,不要讓它欺騙你--JIT會決定在這裏做什麼。它有機會爲每個(TTResult)對生成不同的本地代碼。實際上,代碼將爲所有引用類型共享,並且值類型不同。所以,你會結了:

T = string, TResult = int (native code #1) 
T = Stream, TResult = byte (native code #2) 
T = string, TResult = byte (native code #2) 
T = Stream, TResult = string (native code #3) 

說了這麼多,如果你想限制你的擴展方法引用類型,這樣做的:

public static TResult GetOrDefaultIfNull<T, TResult> 
    (this T obj, Func<T, TResult> getValue, TResult defaultValue) 
    where T : class 

仍然會在一個盒子IL,但不要擔心 - 實際上不會發生拳擊。畢竟,什麼可以被裝箱?您提供了一個引用,並且引用本身永遠不會被裝箱 - 只有值類型值被裝箱。

+0

有趣的是,它不是拳擊。此代碼編譯爲: int i = 1; i.GetOrDefaultIfNull((o)=> o.ToString(),「」); 感謝「Where T:class」這是我真正想要的。 – 2010-04-09 17:34:02

2

簡而言之,代碼中沒有任何東西需要裝箱。有情況下,拳擊是不可避免的,並且在某些情況下還有額外的操作碼用於彌合值/ ref類型(constrained)之間的差距。

但在這種情況下,沒有實際需要拳擊(JIT可以刪除幾個盒子般的情況 - 但不是全部,可悲)