将结构体转换为通用接口时是否存在装箱/拆箱?

发布于 2024-11-03 01:38:58 字数 910 浏览 4 评论 0原文

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

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

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

但是通用接口呢?

例如,int 派生自 IComparableIComparable

假设我有以下代码:

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 (或任何采用该类型的通用接口的函数)是否意味着会有装箱?

谢谢。

Possible Duplicate:
Structs, Interfaces and Boxing

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

Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type.

But what about generic interfaces?

For example, int derives from both IComparable and IComparable<int>.

Let's say I have the following code:

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)
}

Does bar (or any function taking a non-generic interface) means there will be boxing?

Does foo (or any function taking a generic interface on the type) means there will be boxing?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

弥枳 2024-11-10 01:38:58

每当将结构强制转换为接口时,它都会被装箱。 IComparable的目的是是允许这样的事情:

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

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

Any time a struct is cast to an interface, it is boxed. The purpose of IComparable<T> is to allow for something like:

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

When used in that fashion, the struct will be passed as a struct (via the generic type parameter) rather than as an interface, and thus will not have to be boxed. Note that depending upon the size of the struct, it may sometimes be better to pass by value and sometimes by reference, though of course if one is using an existing interface like IComparable one must pass as the interface demands.

固执像三岁 2024-11-10 01:38:58

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

您可以看出某物是值类型,因为函数中所做的更改不会在函数外部保留。调用函数时会复制对象的值,并在函数结束时将其丢弃。

您可以看出某个东西是引用类型,因为函数中所做的更改会保留在函数外部。调用函数时,对象的值不会被复制,并且在该函数结束后仍然存在。

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

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

有了这些知识,我们就可以证明将结构转换为接口(通用或非通用)时确实会发生装箱

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 更改为 ISomeInterface,您仍然会有相同的行为。

First, a short (and probably incomplete) primer on value types, reference types, and boxing.

You can tell that something is a value type because changes made in a function do not persist outside the function. The value of the object is copied when the function is called, and thrown away at the end of that function.

You can tell that something is a reference type because changes made in a function persist outside the function. The value of the object is not copied when the function is called, and exists after the end of that function.

If something is boxed, a single copy is made, and seated within a reference type. It effectively changes from a value type to a reference type.

Note that this all applies to instanced state, i.e. any non-static member data. Static members are not instanced state, and have nothing to do with reference types, value types, or boxing. Methods and properties that don't use instanced state (for example, ones that use only local variables or static member data) will not operate differently differently on reference types, value types, or when boxing occurs.

Armed with that knowledge, here is how we can prove that boxing does occur when converting a struct to an interface (generic or not):

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);
    }
}

The output looks like this:

0
0
1
2

This means that boxing occurs only when doing the conversion:

  • The first two calls do the boxing upon each call.
  • The second two calls already have a boxed copy, and boxing does not occur upon each call.

I won't bother duplicating all the code here, but if you change ISomeInterface<T> to ISomeInterface, you'll still have the same behavior.

软的没边 2024-11-10 01:38:58

答案摘要

我对泛型接口和装箱/拆箱感到困惑,因为我知道 C# 泛型使我们能够生成更高效的代码。

例如,事实上 int 实现了 IComparable IComparable 对我来说意味着:

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

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

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

从现在开始,我毫无疑问地知道将结构体转换到接口中将意味着装箱。

但是,IComparable 应该如何比 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 的答案为我提供了一个我将使用的测试示例在家里。一旦获得有意义的结果,我将立即完成此摘要(我想我将尝试使用按引用传递语义来查看所有这些是如何工作的......)

Summary of answers

My confusion about generic interfaces and boxing/unboxing came from the fact I knew C# generics enabled us to produce more efficient code.

For example, the fact int implements IComparable<T> and IComparable meant to me:

  • IComparable was to be used with old, pre-generics code, but would mean boxing/unboxing
  • IComparable<T> was to be used to generics enabled code, supposedly avoiding boxing/unboxing

Eric Lippert's comment is as simple, clear and direct as it can be:

Generic interface types are interface types. There's nothing special about them that magically prevents boxing

From now, I know without doubt that casting a struct into an interface will imply boxing.

But then, how IComparable<T> was supposed to work more efficiently than IComparable?

This is where supercat's answer (edited by Lasse V. Karlsen) pointed me to the fact generics were more like C++ templates than I thought:

The purpose of IComparable is to allow for something like:

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

Which is quite different from:

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

Or even:

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

My guess is that for the first prototype, the runtime will generate one function per type, and thus, avoid boxing issues when dealing with structs.

Whereas, for the second prototype, the runtime will only generate functions with an interface as a parameter, and as such, do boxing when T is a struct. The third function will just box the struct, no more, no less.

(I guess this is where C# generics combined with C# structs show their superiority when compared with Java type-erasure generics implementation.)

Merlyn Morgan-Graham's answer provided me with an example of test I'll play with at home. I'll complete this summary as soon as I have meaningful results (I guess I'll try to use pass-by-reference semantics to see how all that works...)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文