Is everything an object in .NET and C#?

In .NET and C# all is object.

Simply said.

Even a value type, a struct, an interface and an enum.

One can not approve, but the point is that everything is object, except pointers/references, and literals from binary files, even CPU optimized primitive types, since it is the OOP Theory as well as the. NET specifications and therefore the facts.

About classes and types

From the dotnet/csharplang/Type:

Value types differ from reference types in that variables of the value types directly contain their data, whereas variables of the reference types store references to their data, the latter being known as objects. With reference types, it is possible for two variables to reference the same object, and thus possible for operations on one variable to affect the object referenced by the other variable. With value types, the variables each have their own copy of the data, and it is not possible for operations on one to affect the other.

C#'s type system is unified such that a value of any type can be treated as an object. Every type in C# directly or indirectly derives from the object class type, and object is the ultimate base class of all types. Values of reference types are treated as objects simply by viewing the values as type object. Values of value types are treated as objects by performing boxing and unboxing operations (Boxing and unboxing).

That said in fact that value types are object themselves, even if in IL they are manipulated by references, that is called an "unified type system". That said that all is object, but some are used via references to values and others via values. Thus C# is True OOP but not Pure, mainly for optimizations purpose.

In IL all is a class. All. Except underlying pointers and thus CPU registers, and literals before associated to an object.

We can check if any variable is type of object and all is always true (Fiddle)

All strings, numbers, enums, structs, classes and interfaces to objects are objects being type of object. It is what is called being a True OOP Language. But C# is not Pure OOP because of primitive value types, numbers and strings optimizations.

enum Enumeration { V1, V2 }

struct Structure { string person; }

interface IClass { }

class Class : IClass { }

public static void Main()
string myStringInstance = "test";
int myIntegerInstance = 10;
var myEnumInstance = Enumeration.V1;
var myListInstance = new List();
var myStructInstance = new Structure();
var myClassInstance = new Class();
var myInterface = (IClass)myClassInstance;

Console.WriteLine(myStringInstance is object);
Console.WriteLine(myIntegerInstance is object);
Console.WriteLine(myEnumInstance is object);
Console.WriteLine(myListInstance is object);
Console.WriteLine(myStructInstance is object);
Console.WriteLine(myClassInstance is object);
Console.WriteLine(myInterface is object);

Now let's see the differences between a class and a struct at the IL low-level generated code, before converted to the target machine code:

struct Struct { public int Value; }

class Class { public int Value; }

public static void Main()
var myClass = new Class();
myClass.Value = 10;

var myStruct = new Struct();
myStruct.Value = 20;

This is what it looks like:

.maxstack 2
.locals init (
[0] class Program/Class myClass,
[1] valuetype Program/Struct myStruct

// Class @class = new Class();
IL_0001: newobj instance void Program/Class::.ctor()
IL_0006: stloc.0

// @class.Value = 10;
IL_0007: ldloc.0
IL_0008: ldc.i4.s 10
IL_000a: stfld int32 Program/Class::Value

// Console.WriteLine(@class.Value);
IL_000f: ldloc.0
IL_0010: ldfld int32 Program/Class::Value
IL_0015: call void [mscorlib]System.Console::WriteLine(int32)

IL_001b: ldloca.s 1

// Struct @struct = default(Struct);
IL_001d: initobj Program/Struct

// @struct.Value = 20;
IL_0023: ldloca.s 1
IL_0025: ldc.i4.s 20
IL_0027: stfld int32 Program/Struct::Value

// Console.WriteLine(@struct.Value);
IL_002c: ldloc.1
IL_002d: ldfld int32 Program/Struct::Value
IL_0032: call void [mscorlib]System.Console::WriteLine(int32)

So let's say that here, at this level, there is no real difference between an object instance of a class and an object instance of a structure because the compiler does the job of treating them according to their gender. And so that they have the behavior of reference type or value type. Otherwise there, in assembly, there is not really any difference any more except when it is necessary but all is pointers. Almost everything, way of speaking, towards memory cases essentially via CPU registers.



As we can see, class or struct, it is an object, handled in two ways, but in the same way, as in a two-lane road.

About objects and instances

Terms object and instance can be considered synonyms, but they are not really synonymous from the point of view of scrupulous terminology. The object is the concrete materialization of the type (concept or idea) in the real world (the virtual computer world, thus the physical memory). The instance is rather the mental existence of this object, in the human mind, therefore in the software code. So these words are "synonyms", but:

An object (existence) is an instance (presence) of a type (thought).

In C# .NET managed code, there is no pointer. Pointers are not objects. Pointers are the address of objects stored in memory. In C# we use references that are hidden pointers to forget to manage them. A pointer is just a memory cell address like in a town. We could say that they are objects, but not in the sense of objects in OOP: these pointers, via the references, allow us to create, manipulate and destroy the objects that we use to build applications that runs on a machine to take data, process data and output data and/or control another device.

In OOP, all is object:

An object is an instance of a type.

The words object and instance can be considered synonyms, but they are not really synonymous from the point of view of scrupulous terminology. The object is the concrete materialization of the type or concept or idea in the real world, in the physical memory. The instance is the mental existence of this object, in the human mind, in the software code. So these words are "synonyms", but an object (existence) is an instance (presence) of a type (thought).

Words type and class or struct are not synonyms. One can consider object and instance synonyms, but not these words: class, struct, enum or interface are types. For example, a chair and a table are furniture, but chair and furniture are not synonyms. One can imagine that the word type is the "root class" (parent concept) and words class or struct... are "child classes" (child concepts), in terms of linguistic. So they are not synonyms: there is a level of abstraction that make the difference, like in a tree of terminology.

An instance (the data in the mind to be manipulated by the code) of a struct type (the static definition) is an object (in the computer memory).

One can find answers to fundamental questions by studying the basics up to Intel. To understand the motor of the car we need to open the hood, not to dissect the steering wheel or the tires nor the armchairs. To understand the underlying of virtual .NET we need to study the CPU itself, and that, is fascinating, else we can easily lost time and misunderstood things with confusion and non-sense - same for C# based on OOP Theory.

Sometimes source of knowledge the web as well as in books may be partial or misleading due to errors, oversight, misunderstanding, confusion, or ignorance. Hence the importance of starting with few sources of few experts, like one or two reliable and reputable origin, from the basics, and books or professional courses are the best, rather than scattering. Once mastered, we can open the field and domains with more and don't be scared about discrepancies or contradictions.

About boxing and unboxing

A variable, an object in OOP, that is an instance of a struct or a class, can be boxed to object and unboxed from object : C# Guide. Essentially, unboxed, a value type is an object that doesn't look like an object (it is "shrunken") in order to be optimized for processing by our silicon microprocessors, so there are a lot of speed and memory improvements in the IL code and therefore in the targeted machine code. Boxed, this value type is a full object as such, put in an entire box, and being no longer cheap. A boxed value type is used as the full object without optimizations and sepcial behaviors: it is now a reference we manipulate to access the embedded value in the "original" class.

An unboxed value is the value itself ie the memory cell itself if an integer, in the RAM memory and/or CPU register being x32 or nowadays x64 (one cell here). A boxed value is a reference to a full object instance embedding this value (many cells). The unboxed is manipulated without using "all the full" but the boxed use that. Thus this last is slower and bigger. But an apple remains an apple. For example, if a method requires an object instance being of type Object, we must box an integer, because it only accepts the root type of all.

That means that boxing take an optimized ie a not-pure object like an integer value (a one memory cell) to create a full object Int32. Thus this one-cell memory in the stack in assigned to the int property of the new Int32 object in the heap.

Isn't boxing & unboxing a special case of direct casting?

We call that boxing and unboxing because once boxed to object type, we can unbox without having a compiler type conversion error as any type mismatch will be raised at runtime.

From OpCodes.Box Field:

Converts a value type to an object reference (type O).

The stack transitional behavior, in sequential order, is:

  1. A value type is pushed onto the stack.
  2. The value type is popped from the stack; the box operation is performed.
  3. An object reference to the resulting "boxed" value type is pushed onto the stack.

A value type has two separate representations within the Common Language Infrastructure (CLI):

  • A 'raw' form used when a value type is embedded within another object or on the stack.
  • A 'boxed' form, where the data in the value type is wrapped (boxed) into an object so it can exist as an independent entity.

The box instruction converts the 'raw' (unboxed) value type into an object reference (type O). This is accomplished by creating a new object and copying the data from the value type into the newly allocated object. valTypeToken is a metadata token indicating the type of the value type on the stack.

In terms of machine code material architecture dependant as with an Intel-type microprocessor, it is nothing more than using a memory pointer after performing all the specified processings.

Here is another sample:

int valueInt = 10;
double valueDouble = (double)valueInt;
object instance = (object)valueInt;
int value = (int)instance;

IL generated code is:

.method private hidebysig static 
void Test () cil managed
.maxstack 1
.locals init (
[0] int32 valueInt,
[1] float64 valueDouble,
[2] object 'instance',
[3] int32 'value'

// int num = 10;
IL_0001: ldc.i4.s 10
IL_0003: stloc.0

// double num2 = num;
IL_0004: ldloc.0
IL_0005: conv.r8
IL_0006: stloc.1

// object obj = num;
IL_0007: ldloc.0
IL_0008: box [mscorlib]System.Int32
IL_000d: stloc.2

// int num3 = (int)obj;
IL_000e: ldloc.2
IL_000f: unbox.any [mscorlib]System.Int32
IL_0014: stloc.3

IL_0010: ret

From OpCodes.Unbox Field:

Converts the boxed representation of a value type to its unboxed form.

The stack transitional behavior, in sequential order, is:

  1. An object reference is pushed onto the stack.
  2. The object reference is popped from the stack and unboxed to a value type pointer.
  3. The value type pointer is pushed onto the stack.

A value type has two separate representations within the Common Language Infrastructure (CLI):

  • A 'raw' form used when a value type is embedded within another object.
  • A 'boxed' form, where the data in the value type is wrapped (boxed) into an object so it can exist as an independent entity.

The unbox instruction converts the object reference (type O), the boxed representation of a value type, to a value type pointer (a managed pointer, type &), its unboxed form. The supplied value type (valType) is a metadata token indicating the type of value type contained within the boxed object.

Unlike Box, which is required to make a copy of a value type for use in the object, unbox is not required to copy the value type from the object. Typically it simply computes the address of the value type that is already present inside of the boxed object.

Since classes and structures are in fact the same thing, of course managed differently, but being only references and "hidden-references" (hidden and "hidden-hidden" memory pointers to forget to manage them as well as accessing and using, and to delegate this to the CLR), boxing and unbowing of value-types and non-value-types is essentially the same at this low level of operation, so the IL code does not differentiate between the two.

About literals and binary file

Concerning literals, they can't be considered as objects because they are hard-coded in the EXE binary. The running machine code loads a literal value (or its address to be associated to the new created object) from the data-segment in a register and/or in a variable reserved cell address when we write int a = 10; : the 10 is in the EXE data segment loaded at startup in memory. That, is not an object in term of OOP: it just some raw contiguous bytes (integer, string, arrays of integral types): literals are not variables and nothing about variables applies to literals which are pure raw data without any abstraction or encapsulation.

.maxstack 1
.locals init ( [0] string str )

// string str = "This a literal test string for Stack Overflow!";
IL_0001: ldstr "Test"
IL_0006: stloc.0

// Console.WriteLine(str);
IL_0007: ldloc.0
IL_0008: call void [mscorlib]System.Console::WriteLine(string)

This IL instruction in the code-segment loads the "This is a literal test..." raw bytes stored in the binary EXE file in a str object instance created and being of type string and referenced by the memory address pointer in the stack at position "0", and then this local reference is passed to the Console method.

Here is a compiler generated EXE dump, wagons of raw bytes stored in the data segment loaded when the process started and used to create the new string instance like using a scanner to create an image file:

enter image description here

The raw bytes of a file are not objects as such as considered in OOP: they become objects when instances are created once the application is started, to load pre-defined data or to handle files. They are just raw bytes before the program is running, not objects, not yet.

About OOP and machine code

All that: .NET, C#, OOP, casting, boxing, unboxing... is just high-level language sugar over machine code to allow humans to be able to do things better and simpler but more complex and more powerful in less time.

We must keep in mind that object (tree) can mean two things : object instance in term of OOP (plant), and C# root for all classes/structs/enums object (graph). But that's in reality the same thing because all is object at the highest level of abstraction. Objects instance of a type being a reference type or a value type, are objects, in the two cases. Its upper typeof is object : a struct, a class and a enum is type of object. Always. We have checked that by code and see what is the truth.

In C# .NET OOP, all objects are type of Object class, and any struct is in fact a class, a special class: for IL it is a sub-class of Object. Also any interface is a class, a special class: for IL it is a sub-class of Object. This said in a simplified way.

By writting Object we ensure that we talk about the root class type that is the ancestor of all .NET types (classes, structs, enums, numbers). When we talk about objects we refer to instance of type being of this System.Object root class. Also objects types can be references or value type: that is how the compiler manage special cases and behaviors and optimizations... Thus this comment is misleading. Forget it.

In OOP, everything is an object. This is the theory. Even value types are objects, specials compared to other classes. Everything is explained in duplicates. It is impossible to understand in such fundamental low-level objects nature, memory as well as coding, without learning this theory and how the motherboard works. Impossible without confusion and asking the same questions eternally, in my opinion. It is like trying to understand what are stars without studying atoms.

In OOP, everything is an object. We manipulate objects that have properties or attributes and operations or methods. Even in non-OOP like structured and functionnal. In the end everything is an object whatever its specialized type. The smallest object, the computer atom, is the memory cell and the bit box. Over 0's and 1's of this silicon microprocessor era, the smallest object a computer can store is defined by its register size: 8, 16, 32, 64... bits. This is the basic object: byte word dword qword... All is matter.

An object (an object is an instance of a class) of type ValueType is of type a class that inherits from object C# class. Thus all is class is C#, even interfaces (in IL).

What is it if not an object? In life and in the universe, everything is object, movement, construction, interaction, evolution, destruction... Any event relating to a phenomenon requires a physical support, and this support is called object, intsance of the phenomenon (concepts and laws). Nothing exists without matter. Those who say otherwise are just liars. So if a value-type, integral/struct/str, that is at the top level a class aka an object, otherwise what is it? A spirit ? But spirit is matter: the object, alive (made and moving) as well as dead (des not move and is garbadged)

It is the OOP Theory that does that. It is those who developed this theory who stated that. I just agree. It's the same with the Cell Theory which states that all life comes from the cell (DNA/RNA/Organites) and nothing else than the laws like gravity.

In OOP everything is object = Object even in C# where all is an object of type a class even integral types, structs, numbers and enums as well as strings. Nothing else. It does not matter at this level of abstraction what nuances we bring to the types of objects.

In contrast to that, at low-level, in virtual IL assembly and in CPU-ASM machine code, numbers are simple bytes values in the CPU internal registers, thus they are not OOP objects... But such consideration without knowing what about we call is confusing because C# manipulates objects, all is object, even once compiler optimizations are done unlike in Haskell or Smalltalk, if I remember and don't say an error about these true and pure OOP languages.

DotNet is not pure OOP. For example, when we change the value of an integer, the IL code changes the content of the cell at the address reserved for this value. It uses the same address. It just do what will become a wimple MOV addr, 10. It does not create a copy using another cell or object by putting to trash the old cell or object.


I encourage anybody to study the foundations of this OOP theory: abstraction, encapsulation, inheritance and polymorphism.

As well as the foundations of computers based on silicium microprocessors on a motherboard having some memory.

But please, don't ask me what is the ancestor of object...

The previous and following links are for information and demonstration only. Some are trustworthy, others may contain errors, contradictions or ignorance on the subject, and also opinions, true or false, sometimes difficult to bring into real reality.

Also keep in mind that I could have misconstrued and made inaccuracies or even imperfections, especially since I'm not so good in English, not my mother tongue and I have no oral skills, and my memories in Assembly are old and in old computers before the x64 era, and that I did not too much invested in the IL/CLR study, but what I explained, basically, it's like that, pretty much.


CC BY-SA 4.0 Original Post