将结构强制转换为泛型接口时是否存在装箱/取消装箱

本文关键字:存在 是否 取消装箱 泛型接口 结构 转换 | 更新日期: 2023-09-27 17:52:42

可能重复:
结构、接口和装箱

从MSDN:http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx

装箱是将值类型转换为类型对象或由该值类型实现的任何接口类型的过程。

但是通用接口呢?

例如,int来源于IComparableIComparable<int>

假设我有以下代码:

void foo(IComparable value)    { /* etc. */ }
void bar(IComparable<T> value) { /* etc. */ }
void gizmo()
{
   int i = 42;
   bar(i); // is `i` boxed? I'd say YES
   foo(i); // is `i` boxed? I fear it is (but I hope for NO)
}

bar(或任何采用非通用接口的函数(是否意味着会有装箱?

foo(或任何在类型上使用泛型接口的函数(是否意味着将存在装箱?

谢谢。

将结构强制转换为泛型接口时是否存在装箱/取消装箱

任何时候将结构转换为接口时,都会将其装箱。IComparable<T>是为了允许这样的东西:

void bar<T>(T value) where T : IComparable<T> { /* etc. */ }

当以这种方式使用时,结构将作为结构(通过泛型类型参数(而不是接口传递,因此不必装箱。请注意,根据结构的大小,有时通过值传递,有时通过引用传递可能更好,当然,如果使用像IComparable这样的现有接口,则必须根据接口要求传递。

首先,对值类型、引用类型和装箱进行简短(可能不完整(的入门。

您可以判断某个东西是一种值类型,因为在函数中所做的更改不会在函数之外持久存在。对象的值在调用函数时被复制,并在该函数结束时被丢弃。

您可以判断某个东西是引用类型,因为在函数中所做的更改会在函数之外持续存在。调用函数时不会复制对象的值,该值存在于该函数结束之后。

如果某个东西被装箱,则会制作一个副本,并放置在引用类型中。它有效地从值类型更改为引用类型。

请注意,这一切都适用于实例化状态,即任何非静态成员数据。静态成员不是实例化状态,与引用类型、值类型或装箱无关。不使用实例化状态的方法和属性(例如,仅使用局部变量或静态成员数据的方法和特性(在引用类型、值类型或装箱时的操作不会有不同。

有了这些知识,我们可以证明装箱在将结构转换为接口(无论是否通用(时确实发生

using System;
interface ISomeInterface<T>
{
    void Foo();
    T MyValue { get; }
}
struct SomeStruct : ISomeInterface<int>
{
    public void Foo()
    {
        this.myValue++;
    }
    public int MyValue
    {
        get { return myValue; }
    }
    private int myValue;
}
class Program
{
    static void SomeFunction(ISomeInterface<int> value)
    {
        value.Foo();
    }
    static void Main(string[] args)
    {
        SomeStruct test1 = new SomeStruct();
        ISomeInterface<int> test2 = test1;
        // Call with struct directly
        SomeFunction(test1);
        Console.WriteLine(test1.MyValue);
        SomeFunction(test1);
        Console.WriteLine(test1.MyValue);
        // Call with struct converted to interface
        SomeFunction(test2);
        Console.WriteLine(test2.MyValue);
        SomeFunction(test2);
        Console.WriteLine(test2.MyValue);
    }
}

输出如下:

0
0
1
2

这意味着装箱只在进行转换时发生:

  • 前两次呼叫在每次呼叫时进行拳击
  • 后两个调用已经有一个装箱副本,并且不会在每个调用上进行装箱

我不会在这里重复所有的代码,但如果将ISomeInterface<T>更改为ISomeInterface,您仍然会有相同的行为。

答案摘要

我对泛型接口和装箱/拆箱的困惑来自于我知道C#泛型使我们能够生成更高效的代码。

例如,int实现IComparable<T>IComparable对我来说意味着:

  • IComparable将与旧的泛型前代码一起使用,但这意味着装箱/取消装箱
  • IComparable<T>用于泛型启用的代码,据说可以避免装箱/取消装箱

Eric Lippert的评论尽可能简单、清晰和直接:

通用接口类型是接口类型。它们没有什么特别的地方可以神奇地阻止拳击

从现在起,我毫无疑问地知道,将结构强制转换为接口将意味着装箱。

但是,IComparable<T>应该如何比IComparable更有效地工作呢?

这就是supercat的答案(由Lasse V.Karlsen编辑(向我指出的事实,泛型比我想象的更像C++模板:

IComparable的目的是允许这样的东西:

   void bar<T>(T value) where T : IComparable<T> { /* etc. */ }

这与截然不同

   void bar(IComparable<T> value) { /* etc. */ }

甚至:

   void bar(IComparable value) { /* etc. */ }

我的猜测是,对于第一个原型,运行时将为每个类型生成一个函数,从而避免在处理结构时出现装箱问题。

然而,对于第二个原型,运行时将只生成以接口为参数的函数,因此,当T是结构时,执行装箱。第三个函数只会框住结构,不多也不少。

(我想这就是与Java类型的擦除泛型实现相比,C#泛型与C#结构相结合显示其优势的地方。(

Merlyn Morgan Graham的回答为我提供了一个在家里进行测试的例子。一旦我得到有意义的结果,我就会完成这个摘要(我想我会尝试使用引用传递语义来了解所有这些是如何工作的…(