In C#, Structs are managed in terms of values, and objects are in reference. From my understanding, when creating an instance of a class, the keyword new causes
ValueType and structures are something special in C#. Here I'm showing you what happens when you new something.
Here we have the following
Code
partial class TestClass {
public static void NewLong() {
var i=new long();
}
public static void NewMyLong() {
var i=new MyLong();
}
public static void NewMyLongWithValue() {
var i=new MyLong(1234);
}
public static void NewThatLong() {
var i=new ThatLong();
}
}
[StructLayout(LayoutKind.Sequential)]
public partial struct MyLong {
const int bits=8*sizeof(int);
public static implicit operator int(MyLong x) {
return (int)x.m_Low;
}
public static implicit operator long(MyLong x) {
long y=x.m_Hi;
return (y<>bits);
return y;
}
public MyLong(long x) {
this=x;
}
uint m_Low;
int m_Hi;
}
public partial class ThatLong {
const int bits=8*sizeof(int);
public static implicit operator int(ThatLong x) {
return (int)x.m_Low;
}
public static implicit operator long(ThatLong x) {
long y=x.m_Hi;
return (y<>bits);
}
public ThatLong() {
int i=0;
var b=i is ValueType;
}
uint m_Low;
int m_Hi;
}
And the generated IL of the methods of the test class would be
IL
// NewLong
.method public hidebysig static
void NewLong () cil managed
{
.maxstack 1
.locals init (
[0] int64 i
)
IL_0000: nop
IL_0001: ldc.i4.0 // push 0 as int
IL_0002: conv.i8 // convert the pushed value to long
IL_0003: stloc.0 // pop it to the first local variable, that is, i
IL_0004: ret
}
// NewMyLong
.method public hidebysig static
void NewMyLong () cil managed
{
.maxstack 1
.locals init (
[0] valuetype MyLong i
)
IL_0000: nop
IL_0001: ldloca.s i // push address of i
IL_0003: initobj MyLong // pop address of i and initialze as MyLong
IL_0009: ret
}
// NewMyLongWithValue
.method public hidebysig static
void NewMyLongWithValue () cil managed
{
.maxstack 2
.locals init (
[0] valuetype MyLong i
)
IL_0000: nop
IL_0001: ldloca.s i // push address of i
IL_0003: ldc.i4 1234 // push 1234 as int
IL_0008: conv.i8 // convert the pushed value to long
// call the constructor
IL_0009: call instance void MyLong::.ctor(int64)
IL_000e: nop
IL_000f: ret
}
// NewThatLong
.method public hidebysig static
void NewThatLong () cil managed
{
// Method begins at RVA 0x33c8
// Code size 8 (0x8)
.maxstack 1
.locals init (
[0] class ThatLong i
)
IL_0000: nop
// new by calling the constructor and push it's reference
IL_0001: newobj instance void ThatLong::.ctor()
// pop it to the first local variable, that is, i
IL_0006: stloc.0
IL_0007: ret
}
The behaviour of the methods are commented in the IL code. And you might want to take a look of OpCodes.Initobj and OpCodes.Newobj. The value type is usually initialized with OpCodes.Initobj, but as MSDN says OpCodes.Newobj would also be used.
description in OpCodes.Newobj
Value types are not usually created using newobj. They are usually allocated either as arguments or local variables, using newarr (for zero-based, one-dimensional arrays), or as fields of objects. Once allocated, they are initialized using Initobj. However, the newobj instruction can be used to create a new instance of a value type on the stack, that can then be passed as an argument, stored in a local, and so on.
For each value type which is numeric, from byte to double, has a defined op-code. Although they are declared as struct, there's some difference in the generated IL as shown.
Here are two more things to mention:
ValueType itself is declared a abstract class
That is, you cannot new it directly.
structs cannot contain explicit parameterless constructors
That is, when you new a struct, you would fall into the case above of either NewMyLong or NewMyLongWithValue.
To summarize, new for the value types and structures are for the consistency of the language concept.