2010-04-09 70 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可以删除几个盒子般的情况 - 但不是全部,可悲)