I wouldn’t exactly call boxing a relic. It’s used any time a value type is stored in an object or accessed as an interface it implements. You will see it more in TinyCLR and NETMF given the lack of generics. The big one you called out is
HashTable, and so on. Storing an
int in there for example boxes the int.
Value types in C# are
structs while reference types are
classes. So boxing will only happen when you use a struct.
enum are all structs, so also value types.
In many desktop implementations of the CLR value types themselves are not stored on the heap until they are boxed. They usually exist on the stack as arguments or locals or embedded in a reference type. In our implementations here, as of today, value types are also allocated from the same managed heaps as reference types so the distinction is blurred a bit in practice. Of course, value type semantics are preserved such as receiving a copy and not a reference (unless you explicitly use
You can take a look at the IL to see the
unbox instructions. In the
Developer Command Prompt for VS 2017 that gets installed with VS, you can run
ildasm to open a managed assembly and see its IL code. Keep in mind that managed assemblies are post-processed before being sent to the device.
As @Mike was getting at, I would not worry too much about boxing and unboxing outside of specific benchmarks and use cases that identify it as a bottleneck.
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing is a good starting point reference.