Reputation: 63905
I recently came across this Stackoverflow question: When to use struct?
In it, it had an answer that said something a bit profound:
In addition, realize that when a struct implements an interface - as Enumerator does - and is cast to that implemented type, the struct becomes a reference type and is moved to the heap. Internal to the Dictionary class, Enumerator is still a value type. However, as soon as a method calls GetEnumerator(), a reference-type IEnumerator is returned.
Exactly what does this mean?
If I had something like
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!?
What exactly are the detailed semantics of a value-type structure being treated like a reference-type?
Upvotes: 15
Views: 9452
Reputation: 415
I'm replying your post about your experiment on 2013-03-04, though I might be a bit late :)
Keep this in mind: Every time you assign a struct value to a variable of an interface type (or return it as an interface type) it will be boxed. Think of it like a new object (the box) will be created on the heap, and the value of the struct will be copied there. That box will be kept until you have a reference on it, just like with any object.
With behavior 1, you have the Biz auto property of type IFoo, so when you set a value here, it will be boxed and the property will keep a reference to the box. Whenever you get the value of the property, the box will be returned. This way, it mostly works as if Foo would be a class, and you get what you expect: you set a value and you get it back.
Now, with behavior 2, you store a struct (field tmp), and your Biz property returns its value as an IFoo. That means every time get_Biz is called, a new box will be created and returned.
Look through the Main method: every time you see a b.Biz, that's a different object (box). That will explain the actual behavior.
E.g. in line
b.Biz.Foobar=567;
b.Biz returns a box on the heap, you set the Foobar in it to 576 and then, as you do not keep a reference to it, it is lost immediatly for your program.
In the next line you writeline b.Biz.Foobar, but this call to b.Biz will then again create a quite new box with Foobar having the default 0 value, that's what printed.
Next line, variable f earlier was also filled by a b.Biz call which created a new box, but you kept a reference for that (f) and set its Foobar to 123, so that's still what you have in that box for the rest of the method.
Upvotes: 1
Reputation: 62002
Read about boxing and unboxing (search the internet). For example MSDN: Boxing and Unboxing (C# Programming Guide).
See also the SO thread Why do we need boxing and unboxing in C#?, and the threads linked to that thread.
Note: It is not so important if you "convert" to a base class of the value type, as in
object obj = new Foo(); // boxing
or "convert" to an implemented interface, as in
IFoo iFoo = new Foo(); // boxing
The only base classes a struct
has, are System.ValueType
and object
(including dynamic
). The base classes of an enum
type are System.Enum
, System.ValueType
, and object
.
A struct can implement any number of interfaces (but it inherits no interfaces from its base classes). An enum type implements IComparable
(non-generic version), IFormattable
, and IConvertible
because the base class System.Enum
implements those three.
Upvotes: 2
Reputation: 81307
Every declaration of a structure type really declares two types within the Runtime: a value type, and a heap object type. From the point of view of external code, the heap object type will behave like a class with a fields and methods of the corresponding value type. From the point of view of internal code, the heap type will behave as though it has a field this
of the corresponding value type.
Attempting to cast a value type to a reference type (Object
, ValueType
, Enum
, or any interface type) will generate a new instance of its corresponding heap object type, and return a reference to that new instance. The same thing will happen if one attempts to store a value type into a reference-type storage location, or pass it as a reference-type parameter. Once the value has been converted to a heap object, it will behave--from the point of view of external code--as a heap object.
The only situation in which a value type's implementation of an interface may be used without the value type first being converted to a heap object is when it's passed as a generic type parameter which has the interface type as a constraint. In that particular situation, interface members may be used on the value type instance without its having to be converted to a heap object first.
Upvotes: 16
Reputation: 63905
So, I decided to put this behavior to the test myself. I'll give the "results", but I can't explain why things happen this way. Hopefully someone with more knowledge about how this works can come along and enlighten me with a more thorough answer
Full test program:
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
}
}
}
As you can see, by manually boxing/unboxing we get extremely different behavior. I don't completely understand either behavior though.
Upvotes: 0