18

Do interface variables have value-type or reference-type semantics?

Interfaces are implemented by types, and those types are either value types or reference types. Obviously, both int and string implement IComparable, and int is a value type, and string is a reference type. But what about this:

IComparable x = 42;
IComparable y = "Hello, World!";

(The question I was trying to answer was presumably deleted because it asked whether interfaces are stored on the stack or the heap, and, as we should all be aware, it's more constructive to think of differences between value and reference types in terms of their semantics rather than their implementation. For a discussion, see Eric Lippert's The stack is an implementation detail.)

Peter O.
  • 32,158
  • 14
  • 82
  • 96
phoog
  • 42,068
  • 6
  • 79
  • 117
  • Since Peter O. deleted the preamble explaining why I posted this question, I'll add that information in a comment: I wrote up an answer to a similar question that was deleted before I could post the answer. Noting that it is encouraged to ask a question you know the answer to if it's not already on SO, I searched, and found some related questions, but none really emphasized the central point. – phoog Dec 16 '11 at 23:01
  • a simple search on SO could have yielded: http://stackoverflow.com/q/7995606/112407 http://stackoverflow.com/a/3101955/112407 http://stackoverflow.com/a/5757359/112407 all answering the same question and there's pleanty more – Rune FS Dec 16 '11 at 23:12
  • possible duplicate of [Is there Boxing/Unboxing when casting a struct into a generic interface?](http://stackoverflow.com/questions/5757324/is-there-boxing-unboxing-when-casting-a-struct-into-a-generic-interface) – Rune FS Dec 16 '11 at 23:15
  • @RuneFS I saw those questions before I posted. The first link discusses boxing generally; only one of its seven examples concerns an interface reference to a value type. The second link asks whether assigning a class instance to an interface constitutes boxing, which doesn't address the value-type part of the question. – phoog Dec 16 '11 at 23:16
  • The first link has _exactly_ the information needed to answer this question (as part of the question itself): that casting a ValueType to an interface type is a boxing operation. The second link is to an answer (not a question) and the answer quotes the specifications and thereby explains that it's a boxing operation – Rune FS Dec 16 '11 at 23:21
  • @RuneFS the fact that a question or one of its answers has information that could answer another question does not make the other question a duplicate of the first. Further discussion in chat, please. – phoog Dec 16 '11 at 23:36
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/5926/discussion-between-phoog-and-rune-fs) – phoog Dec 16 '11 at 23:36
  • @RuneFS none of the questions are actually a duplicate (just related). – nawfal May 11 '13 at 13:14
  • @phoog this is a more closely related [why-are-interfaces-in-net-reference-types](http://stackoverflow.com/questions/4638367/why-are-interfaces-in-net-reference-types) – nawfal May 11 '13 at 13:15
  • @nawfal if I had found them to be duplicates I wound have voted for a close. They do however answer the question. the 4th example in the list link is the exact situation mentioned here (assigning a value to an interfaced typed variable) – Rune FS May 12 '13 at 09:21
  • @RuneFS I agree had OP read those links he would have got the answer. But I think this deserves as a separate question. The more important question OP should have emphasized is *why* (why are interfaces variables treated as references). – nawfal May 12 '13 at 09:29
  • @nawfal which is why as I wrote I did not vote to close but linked to those questions – Rune FS May 12 '13 at 13:25
  • I believe the importance of a title should not be understated. There are many questions who's title do not necessarily convey the actual topic discussed and/or the underlying problem's resolution. – samus Mar 28 '17 at 16:49
  • @SamusArin thanks for your comment. I agree with it, but I don't understand why you added it here. Are you suggesting the title of this question can be improved? – phoog Mar 28 '17 at 16:54
  • @phoog I was referring to the provided dups. They're titled "Boxing Occurrence in C#", "Is casting to an interface a boxing conversion?", and "Is there Boxing/Unboxing when casting a struct into a generic interface?" respectively. Each one seems to ask of a specific occurrence of the more general question question asked here is all. – samus Mar 28 '17 at 17:00
  • ... this questions seems to address the heart of the (interface) boxing matter ... – samus Mar 28 '17 at 17:02
  • @SamusArin Thanks for clarifying. – phoog Mar 28 '17 at 17:04
  • @phoog NP... Google search brought me here, this post was right near top. Works for me (That's a really interesting paper by Eric Lippert btw). – samus Mar 28 '17 at 17:29

4 Answers4

20

Usually, as per the existing answers, it is a reference-type and requires boxing; there is an exception though (isn't there always?). In a generic method with a where constraint, it can be both:

void Foo<T>(T obj) where T : ISomeInterface {
    obj.SomeMethod();
}

This is a constrained operation, and is not boxed even if it is a value-type. This is achieved via constrained. Instead, the JIT performs the operation as virtual-call for reference-types, and static-call for value-types. No boxing.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • But in this case, the type of `obj` is not `ISomeInterface`, it is `T` (which of course goes to one of the core reasons for implementing generics). – phoog Dec 16 '11 at 23:08
  • 1
    @phoog indeed, it is an odd level in-between - but IMO it is an important aspect, that deserves a mention in the context of the question title. – Marc Gravell Dec 16 '11 at 23:15
  • FYI there are some circumstances where a constrainted.callvirt still causes boxing. It can eliminate most boxings though. The page you linked to describes the situations in which constrained.callvirt boxes. – Eric Lippert Dec 18 '11 at 15:50
  • @Eric Lippert: If a struct implements an interface explicitly, would the constrained callvirt make it possible to access the interface members without boxing and without making an extra call to a generic function? Does C# or vb.net provide any way to exploit such behavior? – supercat Dec 18 '11 at 23:58
  • What you mention is an important point, also alluded to in my answer. If it's important that an object implementing an interface have reference semantics, it's probably better to use an interface type rather than a generic constrained to an interface. That will cause needless boxing of shallowly-immutable structs which would otherwise be compatible with reference semantics anyway, but it wouldn't break program correctness. – supercat Dec 19 '11 at 00:04
  • @Eric since we're talking about interfaces, though, surely it *must* implement the method (hence I omitted the usual caveat). My *understanding* is that the edge-case here would be limited to things that don't override `object.ToString()`, `object.GetHashCode()`, `object.Equals()` - am I wrong? – Marc Gravell Dec 19 '11 at 07:26
  • @supercat I'm unclear what your comment above is trying to say; your comment *seems* to suggest that generic/constraint will cause additional boxing, but that is the *opposite* of the case... – Marc Gravell Dec 19 '11 at 07:29
  • @MarcGravell: I'm saying that using a parameter of interface type rather than generic will ensure boxing, which will in turn ensure reference semantics. Some structs would yield reference semantics even without boxing, so using an interface type (rather than constrained generic) for the purpose of boxing would cause such structs to be handled less efficiently than they otherwise could be. – supercat Dec 19 '11 at 07:44
  • 1
    @supercat hmmm... they won't *really* exhibit reference semantics... concrete example? – Marc Gravell Dec 19 '11 at 07:50
  • @MarcGravell: Sure. A class struct holds a single field TheList of type List, and implements IList where T:U by having all the non-mutating members call the corresponding members of the TheList, while mutating members throw NotSupportedException. If one had one such struct where theList pointed to a particular list, and one made a copy of that struct, the copy would "see" changes in the original list in precisely the same way as would the original. – supercat Dec 19 '11 at 08:06
  • 1
    @supercat no, that is smoke and mirrors; you are talking about the semantics of an **unrelated** object (`TheList`), and has nothing to do with the semantics of the struct. It is only meaningful to talk about the semantics in terms of first-order changes to values **of the entity**, i.e. change the **value** of `TheList`. – Marc Gravell Dec 19 '11 at 08:18
  • @MarcGravell: If a type has mutable value *semantics*, a copy of a variable will be semantically distinct from the original, and there will be some means by which one may be altered without affecting the other. If a type has reference *semantics*, a copy of a variable will be semantically equivalent to the original. There are some holes in the abstraction (e.g. because ReferenceEquals is exposed for all types) but two copies of the same ListWrapStruct would always see e.g. the same Count, and anything that would affect one copy would affect all. – supercat Dec 19 '11 at 15:51
  • 1
    @supercat but again you're talking about an unrelated object (the list); the semantics of the list are not in question. The only interesting change in your example is : assigning the field I a different list (or null). Anything else is talking about something else completely. – Marc Gravell Dec 19 '11 at 15:57
  • 1
    @MarcGravell: My point is that a struct which holds a private immutable reference to a class instance will behave semantically more like the class type it wraps than like a value type. Change the ListWrapStruct above so that the indexed setter will invoke the indexed setter of the wrapped list. Set element 0 of a ListWrapStruct to 3, make a copy of that struct, change element 0 of the copy to 8, and read element 0 of the original. If the ListWrapStruct had value semantics, the original would still read 3. Instead it will read 8. The original example wasn't the greatest... – supercat Dec 19 '11 at 16:04
  • 1
    ...because semantically it was tied to an external list. But change things a little so that a constructor with an int parameter creates and wraps a new List instance which is never exposed elsewhere. Semantically, the struct would behave like a List, and copying the structure would behave like copying a *reference* to a List. – supercat Dec 19 '11 at 16:06
5

A variable or field whose type is IComparable is a reference-type variable or field, regardless of the type of the value assigned to that field. This means that x in the sample code is boxed.

A simple test will demonstrate that. The test is based on the fact that you can only unbox a value type to its original type (and the nullable version of that type):

    [TestMethod, ExpectedException(typeof(InvalidCastException))]
    public void BoxingTest()
    {
        IComparable i = 42;
        byte b = (byte)i;      //exception: not allowed to unbox an int to any type other than int
        Assert.AreEqual(42, b);
        Assert.Fail();
    }

EDIT

On top of that, the C# specification specifically defines reference-type as comprising class types, interface types, array types and delegate types.

EDIT 2

As Marc Gravell points out in his answer, a generic type with an interface constraint is a different case. This doesn't cause boxing.

Community
  • 1
  • 1
phoog
  • 42,068
  • 6
  • 79
  • 117
  • why would you want to post a question only to answer it immediately afterwards? – Rune FS Dec 16 '11 at 22:56
  • @RuneFS Peter O. edited my question, deleting the explanation and the link to http://meta.stackexchange.com/questions/17845/etiquette-for-answering-your-own-question. If you click on the time where it says "edited 10 mins ago" or whatever, you can see the version history -- a fact that took me several months to stumble upon. – phoog Dec 16 '11 at 22:58
  • @RuneFS: Because, as the questioner originally wrote, "it is [encouraged to ask a question you know the answer to](http://meta.stackexchange.com/q/17845/156890)". – Peter O. Dec 16 '11 at 22:59
  • 1
    Actually, there are other exceptions; you can unbox an enum to the underlying type (i.e. you can unbox an `enum Foo {}` directly to an `int`), for example (even though the box **knows** it is a `Foo`, not an `int`) – Marc Gravell Dec 19 '11 at 07:36
5

This is about understanding boxing and unboxing of types. In your example, the int is boxed upon assignment and a reference to that "box" or object is what is assigned to x. The value type int is defined as a struct which implements IComparable. However, once you use that interface reference to refer to the value type int, it will be boxed and placed on the heap. That's how it works in practice. The fact that using an interface reference causes boxing to occur by definition makes this reference type semantics.

MSDN: Boxing and Unboxing

Eben Geer
  • 3,696
  • 3
  • 31
  • 34
  • "the value type int is not a class and therefore does not implement any interfaces": The value type `int` most assuredly implements several interfaces. You might want to check the documentation: `public struct Int32 : IComparable, IFormattable, IConvertible, IComparable, IEquatable` from http://msdn.microsoft.com/en-us/library/system.int32.aspx – phoog Dec 16 '11 at 22:55
  • 1
    You're a little bit right - int === struct Int32. There's no difference until you cast it as one of the implementing interfaces at which point it becomes boxed. – Eben Geer Dec 16 '11 at 23:03
  • 2
    phoog is right. The idea that only classes implement interfaces is completely wrong. Structs and arrays are not classes, but they also implement interfaces. (And of course, interfaces inherit from other interfaces.) – Eric Lippert Dec 18 '11 at 15:53
  • Yes, I see that part of what I said was completely wrong. I'll edit my answer again to be more correct. – Eben Geer Dec 18 '11 at 22:41
2

Variables of interface type will have always have either immutable semantics, mutable reference semantics, or "oddball" semantics (something other than normal reference or value semantics). If variable1 and variable2 are both declared as the same interface type, one performs variable2 = variable1, and one never again writes to either variable, the instance referred to by variable1 will always be indistinguishable from the one referred to be variable2 (since it will be the same instance).

Generic types with interface constraints may have immutable semantics, mutable reference semantics, or "quirky" semantics, but may also have mutable value semantics. This can be dangerous if the interface is not documented as having mutable value semantics. Unfortunately, there is no way to constrain an interface to have either immutable semantics or mutable value semantics (meaning that following variable2 = variable1, it should not be possible to change variable1 by writing variable2, nor vice versa). One could add a "struct" constraint along with the interface constraint, but that would exclude classes which have immutable semantics while not excluding structs that have reference semantics.

supercat
  • 77,689
  • 9
  • 166
  • 211