当扩展System.Object时,如何避免装箱/拆箱?

3

我正在开发一个只适用于引用类型的扩展方法。但是,我认为它目前正在对该值进行装箱和拆箱操作。如何避免这种情况?

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
2个回答

5

那不是拳击。你认为哪里是拳击?如果是因为你看了"=="周围的IL代码,不要被它迷惑- JIT有机会决定在这里做什么。它有机会为每个(T,TResult)对生成不同的本地代码。事实上,该代码将为所有引用类型共享,并针对值类型而异。因此,您最终会得到:

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 中仍然会有一个框,但不用担心 - 实际上不会发生任何装箱操作。毕竟,什么可以被装箱呢?您提供的是一个引用,而引用本身永远不会被装箱 - 只有值类型的值会被装箱。


有趣的是它不是装箱。这段代码已经编译通过: int i = 1; i.GetOrDefaultIfNull((o) => o.ToString(), ""); 感谢"where T : class",正是我在寻找的。 - Robert H.

2

简单来说,那段代码中没有需要装箱的内容。有些情况下是避免不了装箱的,同时也有额外的操作码用于在某些情况下弥合值类型与引用类型之间的差距(constrained)。

但在这种情况下并不需要实际的装箱(JIT可以删除一些类似于装箱的情况,但不是全部,遗憾地是)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接