将结构体转换为通用接口时是否存在Boxing/Unboxing?

pae*_*bal 15 c# generics boxing

可能重复:
结构,接口和拳击

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

Boxing是将值类型转换为类型对象由此值类型实现的任何接口类型的过程.

但是通用接口呢?

例如,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)
}
Run Code Online (Sandbox Code Playgroud)

bar(或任何采用非通用接口的功能)意味着会有拳击吗?

foo(或任何函数采用类型的通用接口)意味着将有拳击?

谢谢.

sup*_*cat 20

无论何时将结构体转换为接口,都会将其装箱.IComparable <T>的目的是允许类似的东西:

void bar<T>(T value) where T : IComparable<T> { /* etc. */ }
Run Code Online (Sandbox Code Playgroud)

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

  • "任何时候将一个结构体转换为一个接口,它被装箱"之后的所有内容都有点不合时宜...但与此同时,它让我意识到一些关于泛型,结构和界面的重要内容,这是一个完整的答案所不具备的东西,即`bar <T>(IComparable <T> value)`和`bar之间的区别<T>(T值)其中T:IComparable <T>`,所以,为什么接口对结构仍然很重要,只要我们正确使用泛型.+1. (2认同)

Mer*_*ham 7

首先,关于值类型,引用类型和装箱的简短(可能是不完整的)入门.

您可以判断某些内容是值类型,因为函数中所做的更改不会在函数外部保留.调用函数时会复制对象的值,并在该函数的末尾抛弃该值.

您可以判断某些内容是引用类型,因为函数中所做的更改将保留在函数外部.调用函数时不会复制对象的值,并且在该函数结束后存在该值.

如果装箱,则会制作一份副本,并在参考类型中就座.它实际上从值类型更改为引用类型.

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

有了这些知识,这就是我们如何证明在将结构转换为接口时发生装箱(通用或非通用):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出如下所示:

0
0
1
2

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

  • 前两次调用在每次调用时都进行装箱.
  • 后两个调用已经有一个盒装副本,每次调用时都不会发生装箱.

我不会打扰这里复制所有的代码,但如果你改变ISomeInterface<T>ISomeInterface,您仍然有相同的行为.


pae*_*bal 6

答案摘要

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

例如,事实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. */ }
Run Code Online (Sandbox Code Playgroud)

这与以下内容截然不同:

   void bar(IComparable<T> value) { /* etc. */ }
Run Code Online (Sandbox Code Playgroud)

甚至:

   void bar(IComparable value) { /* etc. */ }
Run Code Online (Sandbox Code Playgroud)

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

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

(我猜这是与Java类型擦除泛型实现相比,C#泛型与C#结构相结合显示其优越性的地方.)

Merlyn Morgan-Graham的回答为我提供了一个我将在家里玩的测试的例子.一旦我得到有意义的结果,我就会完成这个摘要(我想我会尝试使用pass-by-reference语义来看看它是如何工作的......)