-2

For a simple example:

int a = 1;
a.ToString();
  1. Since a is a value type and ToString method operates on object, may I ask if there is a boxing on a?

  2. More generally, I learnt that value types and reference/object types are two different concepts. Nevertheless, I noticed that there is the ValueType class in C# inheriting from System.Object in which all value types are inheriting from. I am a bit confused at this point: are value types themselves being treated as instances of the ValueType class, or are they being boxed into instances of the ValueType class?

  3. And thus, when does the computer treats value types as an object of a class, and when does not?

J-A-S
  • 368
  • 1
  • 8
  • 2
    Does [this](https://stackoverflow.com/a/3504145/5133585) answer your questions? – Sweeper Jul 20 '21 at 07:22
  • 1
    1) No. 2) and 3) How long have you been programming in C# (so we can pitch the answer at a level that you understand)? – mjwills Jul 20 '21 at 07:25
  • @Sweeper Hi, thank you for your link :) actually I have read and bookmarked that post before I posted this question. Honestly I'm kind of a beginner in C# and would love to see further clarifications – J-A-S Jul 20 '21 at 07:27
  • @mjwills well, half a month I guess? Thank you very much for your attention... – J-A-S Jul 20 '21 at 07:28
  • 1
    If you have been programming for a month, put this aside for a year and come back to it then. There are more important things to get your head around. All you really need to know is that there are value types - where the _object_ is copied by value. And reference types, where the _reference_ is copied by value. And that boxing allows value types to be treated kinda sorta like reference types. – mjwills Jul 20 '21 at 08:03

1 Answers1

3

This is a surprisingly complex area!

  • no, a.ToString() is not boxing, but this is only true so long as the value-type has an override for the method being called; for this reason, it is a good idea to always override the standard methods: ToString, Equals and GetHashCode on structs; it is theoretically possible that the JIT could do extra work here to devirtualize the calls, but: why risk it?
  • in general, things that would cause boxing include:
    • calling a non-overridden method
    • casting the struct to an interface that it implements
    • casting it to object or ValueType or Enum
  • note that "constrained call" can be used to avoid the interface boxing problem - rather than Foo(IBar) consider Foo<T>(T) where T : IBar - the latter uses constrained call, allowing access to methods etc from IBar without having to box

So: a value-type is treated as a value-type so long as it is declared and accessed as that value-type, and the methods used are declared / overridden on that value type; or When using generics as T which is a value-type when used, nothing that constrained call can provide access to methods of generics

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Can you provide a link to more details about this "constrained call" approach? I'd like to read more about that. I would have (apparently incorrectly) assumed that boxing applied there. – mjwills Jul 20 '21 at 08:40
  • @mjwillis if you use sharplap or your favourite IL tool, you'll see that the `T where T : IBar` approach emits "constrained" opcodes before `callvirt`; what that does is discussed here: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained - I believe Roslyn also emits "constrained" before calls on ToString/Equals etc on user-type structs in external libs, so that if the deployed version is different to the build version, it does the most appropriate type of call (no unnecessary boxing if override added, and no "boom" if override removed) – Marc Gravell Jul 20 '21 at 09:02
  • @mjwills: Its explained by Eric Lippert in [this](https://stackoverflow.com/questions/5531948/how-does-a-generic-constraint-prevent-boxing-of-a-value-type-with-an-implicitly) question. – György Kőszeg Jul 20 '21 at 09:45
  • @MarcGravell Thank you for your answer, ```override``` does make things clearer :) May I further ask, since ```Enum``` is a value type, why casting a value type to ```Enum``` causes boxing? Do you mean Enum Class? (may I confirm Enum type and Enum class are two different things?) – J-A-S Jul 21 '21 at 05:54
  • 1
    @J-A-S every `enum` type inherits from `Enum`, but the key phrase from my answer is: "*as that value-type*"; `Enum` is **not** the declared value-type - just like `object` (which all value-types inherit from) is a reference-type, so too is `Enum`, so: casting a **specific** `enum` type to the **general** `Enum` base-type: is a boxing operation; you can see the box in the IL here: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANgHxgDsBXAWwAIAxCCCgbwoEE0KAhVgYQoF8BYAFCCAAgEYkFYQCZ2AQygNBFZZPGqAdAFFSlACoRt5ABQ06AN1m4SMAJQUAvAD4KFqzADcKwTyA – Marc Gravell Jul 21 '21 at 06:58
  • @MarcGravell Thank you for your explanation :) This may be a soft question, but may I ask for your opinion on why C#/.NET is designed in such a way that value types (which are not reference type) are derived from System.ValueType class (in which Object is a reference type) while reference type and value type are treated very differently, and in the meantime we also frequently want to use boxing to treat value types as reference type? How does the design logic goes here? To me, this sounds a bit like running in a circle... Why didn't we just separate the two and use boxing where needed? – J-A-S Jul 21 '21 at 09:31
  • 1
    I would disagree; it isn't that "we also frequently want to use boxing to treat value types as reference type" - but rather: it is useful to *have that as an option*, especially when you consider that before .NET 2, "generics" didn't exist - so without the ability to treat a value-type as an object, huge chunks of the API simply wouldn't work for value-types. In reality, for anything important: people spend a lot of time *avoiding* unnecessary boxing. Having the unified type system (where they all derive from `object`) is what makes it *possible as a fallback*. – Marc Gravell Jul 21 '21 at 09:35
  • @MarcGravell Hi Marc, just one more question: when you said "*a value-type is treated as a value-type so long as ... the methods used are declared / overridden on that value type*", does this includes forms like ```someObject.Equals(valueType)```? Say, for example, is there boxing for the integer 42 in ```System.Object.Equals(someInstance, 42)```? – J-A-S Jul 21 '21 at 13:33
  • @J-A-S that depends whether `someObject` *overloads* `Equals`, i.e. is there a `bool Equals(TheSameType other)` method? – Marc Gravell Jul 21 '21 at 13:53
  • @MarcGravell May I ask why that specific overloading for the same type matters? (as an example, ```int.Equals``` has that overloading while for a plain custom class there is not) – J-A-S Jul 21 '21 at 14:21
  • 1
    @J-A-S for a plain custom class, it *might* be there if the author adds it, but the key word in that question is "class" - there is no boxing overhead in a class having an `bool Equals(object)` method, since it is almost always passed an instance *of the same type*, which will therefore be: a class, i.e. a reference type. It (and `IEquatable`) is often added explicitly in the case of value-types *precisely to prevent the need for boxing when computing equality*. – Marc Gravell Jul 21 '21 at 15:16