当将结构值作为接口值传递时,如何避免装箱

本文关键字:何避免 值传 结构 接口 | 更新日期: 2023-09-27 18:22:12

接口(I)是引用类型,结构(S)是值类型。结构可以实现接口。

public interface I {}
struct S: I {}

假设有一个S的值作为I的参数传递给一个方法。在这种情况下,它必须被装箱。

void Method(I i) {}
void Test() {
   var s = new S();
   this.Method(s); // <---- boxing!
}

在这种情况下有办法避免拳击吗?

当将结构值作为接口值传递时,如何避免装箱

如果将Method的定义更改为:,则可以避免装箱

void Method<T>(T i) where T : I
{
}

这避免了装箱,因为在运行时CLR根据泛型参数的类型专门处理泛型方法。引用类型都可以共享同一个实现,而结构类型都有自己的版本。这意味着Method中依赖于T的所有操作都将考虑具体结构类型的大小。

但是,您必须小心,因为调用System.Object上定义的虚拟方法(如EqualsGetHashCode)会导致i被装箱,因为虚拟方法调度需要方法表指针(尽管JIT在某些情况下可以静态地进行调度)。但是,如果您的结构类型重写了有问题的虚拟方法,那么就不需要进行装箱,因为要调用的方法是静态已知的,因为结构(以及它们的成员)是密封的。

通常,通过进一步约束T以实现IEquatable<T>并使用IEqualityComparer<T>进行比较(例如),可以避免直接调用EqualsGetHashCode

void Method<T>(T i) where T : I, IEquatable<T>
{
    T other = ...
    if(i.Equals(other))    //avoids boxing
    {
    }
}

使用泛型:

public interface I {}
public struct S : I {}
public class Foo
{
    public static void Bar<T>(T i)
        where T : I
    {}
}