结构体实现接口时发生的情况的详细信息

本文关键字:情况 详细信息 实现 接口 结构体 | 更新日期: 2023-09-27 18:02:24

我最近遇到了这个Stackoverflow问题:什么时候使用struct?

在里面,它有一个答案,说了一些深奥的东西:

另外,要认识到,当一个结构体实现了一个接口——asEnumerator执行此操作,并将其强制转换为实现的类型,即结构体成为引用类型并移动到堆中。内部的字典类,Enumerator仍然是一个值类型。然而,一旦当方法调用GetEnumerator()时,引用类型IEnumerator是返回。

这到底是什么意思?

如果我有像

这样的东西
struct Foo : IFoo 
{
  public int Foobar;
}
class Bar
{
  public IFoo Biz{get; set;} //assume this is Foo
}
...
var b=new Bar();
var f=b.Biz;
f.Foobar=123; //What would happen here
b.Biz.Foobar=567; //would this overwrite the above, or would it have no effect?
b.Biz=new Foo(); //and here!?

被当作引用类型对待的值类型结构的详细语义究竟是什么?

结构体实现接口时发生的情况的详细信息

每个结构类型的声明实际上在运行时内声明了两种类型:值类型和堆对象类型。从外部代码的角度来看,堆对象类型的行为类似于具有相应值类型的字段和方法的类。从内部代码的角度来看,堆类型的行为就好像它有一个相应值类型的字段this

尝试将值类型强制转换为引用类型(Object, ValueType, Enum或任何接口类型)将生成其相应堆对象类型的新实例,并返回对该新实例的引用。如果试图将值类型存储到引用类型存储位置,或者将其作为引用类型参数传递,也会发生同样的事情。一旦该值被转换为堆对象,从外部代码的角度来看,它将表现为堆对象。

可以使用值类型的接口实现而不首先将值类型转换为堆对象的唯一情况是,当它作为具有接口类型作为约束的泛型类型参数传递时。在这种特殊情况下,可以在值类型实例上使用接口成员,而不必首先将其转换为堆对象。

阅读关于装箱拆箱(在互联网上搜索)。例如MSDN:装箱和拆箱(c#编程指南)。

参见SO线程为什么我们需要在c#中装箱和拆箱?,以及链接到该线程的线程。

注意:如果你"转换"为值类型的基类,就不那么重要了,如
object obj = new Foo(); // boxing

或"转换"为已实现的接口,如

IFoo iFoo = new Foo(); // boxing

struct仅有的基类是System.ValueTypeobject(包括dynamic)。enum类型的基类是System.EnumSystem.ValueTypeobject

结构体可以实现任意数量的接口(但不能从基类继承接口)。枚举类型实现IComparable(非泛型版本)、IFormattableIConvertible,因为基类System.Enum实现了这三者。

我回复你关于2013-03-04实验的帖子,虽然我可能有点晚了:)

请记住:每次将结构体值赋给接口类型的变量(或将其作为接口类型返回)时,它都会被框起来。可以这样想,在堆上创建了一个新对象(盒子),struct的将在那里复制 。这个方框将一直保存,直到你对它有一个引用,就像对任何对象一样。

对于行为1,您拥有类型为IFoo的Biz auto属性,因此当您在这里设置一个值时,它将被框起来,并且该属性将保持对该框的引用。每当您获得属性的值时,将返回该框。这样,它的工作原理就好像Foo是一个类,你得到了你所期望的:你设置了一个值,然后得到它。

现在,对于行为2,您存储一个结构体(字段tmp),并且您的Biz属性将其值作为IFoo返回。这意味着每次调用get_Biz时,将创建一个新框并返回

查看Main方法:每次看到b.b biz,它都是一个不同的对象(box)。这就解释了实际的行为。

。In line

    b.Biz.Foobar=567;

b。Biz在堆上返回一个框,您将其中的Foobar设置为576,然后,由于您没有保留对它的引用,因此它将立即丢失。

在下一行写b行。但是对b.Biz的调用将再次创建一个全新的框,其中Foobar具有默认的0值,这就是打印的内容。

下一行,前面的变量f也由b.b biz调用填充,该调用创建了一个新框,但是您保留了该(f)的引用并将其Foobar设置为123,因此对于该方法的其余部分,您仍然在该框中拥有它。

所以,我决定亲自测试一下这种行为。我会给出"结果",但我无法解释为什么事情会这样发生。希望有更了解这是如何工作的人可以过来给我一个更彻底的答案

完整测试程序:

using System;
namespace Test
{
    interface IFoo
    {
        int Foobar{get;set;}
    }
    struct Foo : IFoo 
    {
        public int Foobar{ get; set; }
    }
    class Bar
    {
        Foo tmp;
        //public IFoo Biz{get;set;}; //behavior #1
        public IFoo Biz{ get { return tmp; } set { tmp = (Foo) value; } } //behavior #2
        public Bar()
        {
            Biz=new Foo(){Foobar=0};
        }
    }

    class MainClass
    {
        public static void Main (string[] args)
        {
            var b=new Bar();
            var f=b.Biz;
            f.Foobar=123; 
            Console.WriteLine(f.Foobar); //123 in both
            b.Biz.Foobar=567; /
            Console.WriteLine(b.Biz.Foobar); //567 in behavior 1, 0 in 2
            Console.WriteLine(f.Foobar); //567 in behavior 1, 123 in 2
            b.Biz=new Foo();
            b.Biz.Foobar=5;
            Console.WriteLine(b.Biz.Foobar); //5 in behavior 1, 0 in 2
            Console.WriteLine(f.Foobar); //567 in behavior 1, 123 in 2
        }
    }
}
如您所见,通过手动装箱/拆箱,我们得到了非常不同的行为。我不完全理解这两种行为。