结构实现接口时会发生什么的详细信息

Ear*_*rlz 13 .net c# struct boxing value-type

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

在其中,它有一个答案,说了一些有点深刻的东西:

另外,要意识到当一个struct实现一个接口 - 就像Enumerator那样 - 并且被强制转换为该实现类型时,该struct将成为一个引用类型并被移动到堆中.在Dictionary类的内部,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!?
Run Code Online (Sandbox Code Playgroud)

究竟什么值类型结构的详细语义被视为引用类型?

sup*_*cat 12

结构类型的每个声明都在Runtime中声明了两种类型:值类型和堆对象类型.从外部代码的角度来看,堆对象类型的行为类似于具有相应值类型的字段和方法的类.从内部代码的角度来看,堆类型的行为就像它具有this相应值类型的字段一样.

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

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

  • 你应该以某种方式突出显示最后一段,这非常重要. (7认同)

Ear*_*rlz 0

因此,我决定亲自测试一下这种行为。我会给出“结果”,但我无法解释为什么事情会这样发生。希望对它的工作原理有更多了解的人能够提供更全面的答案来启发我

完整测试程序:

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

正如您所看到的,通过手动装箱/拆箱,我们得到了截然不同的行为。但我并不完全理解这两种行为。