Created
January 22, 2018 10:14
-
-
Save gfoidl/14b07dfe8ee5cb093f216f8a85759d88 to your computer and use it in GitHub Desktop.
Struct readonly field
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace ConsoleApp2 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var foo1 = new Foo1(); | |
Console.WriteLine(foo1.Struct()); | |
Console.WriteLine(foo1.ReadOnlyStruct()); | |
var foo2 = new Foo2(); | |
Console.WriteLine(foo2.Struct()); | |
Console.WriteLine(foo2.ReadOnlyStruct()); | |
} | |
} | |
public class Foo1 | |
{ | |
private Point1 _point1 = new Point1(1, 2); | |
private Point2 _point2 = new Point2(3, 4); | |
public int Struct() => _point1.X + _point1.Y; | |
public int ReadOnlyStruct() => _point2.X + _point2.Y; | |
} | |
public class Foo2 | |
{ | |
private readonly Point1 _point1 = new Point1(1, 2); | |
private readonly Point2 _point2 = new Point2(3, 4); | |
public int Struct() => _point1.X + _point1.Y; | |
public int ReadOnlyStruct() => _point2.X + _point2.Y; | |
} | |
public struct Point1 | |
{ | |
public int X { get; } | |
public int Y { get; } | |
public Point1(int x, int y) | |
{ | |
this.X = x; | |
this.Y = y; | |
} | |
} | |
public readonly struct Point2 | |
{ | |
public int X { get; } | |
public int Y { get; } | |
public Point2(int x, int y) | |
{ | |
this.X = x; | |
this.Y = y; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.class public auto ansi beforefieldinit ConsoleApp2.Foo1 | |
extends [System.Runtime]System.Object | |
{ | |
// Fields | |
.field private valuetype ConsoleApp2.Point1 _point1 | |
.field private valuetype ConsoleApp2.Point2 _point2 | |
// Methods | |
.method public hidebysig | |
instance int32 Struct () cil managed | |
{ | |
// Method begins at RVA 0x208e | |
// Code size 24 (0x18) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ldflda valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1 | |
IL_0006: call instance int32 ConsoleApp2.Point1::get_X() | |
IL_000b: ldarg.0 | |
IL_000c: ldflda valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1 | |
IL_0011: call instance int32 ConsoleApp2.Point1::get_Y() | |
IL_0016: add | |
IL_0017: ret | |
} // end of method Foo1::Struct | |
.method public hidebysig | |
instance int32 ReadOnlyStruct () cil managed | |
{ | |
// Method begins at RVA 0x20a7 | |
// Code size 24 (0x18) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2 | |
IL_0006: call instance int32 ConsoleApp2.Point2::get_X() | |
IL_000b: ldarg.0 | |
IL_000c: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2 | |
IL_0011: call instance int32 ConsoleApp2.Point2::get_Y() | |
IL_0016: add | |
IL_0017: ret | |
} // end of method Foo1::ReadOnlyStruct | |
.method public hidebysig specialname rtspecialname | |
instance void .ctor () cil managed | |
{ | |
// Method begins at RVA 0x20c0 | |
// Code size 33 (0x21) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ldc.i4.1 | |
IL_0002: ldc.i4.2 | |
IL_0003: newobj instance void ConsoleApp2.Point1::.ctor(int32, int32) | |
IL_0008: stfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1 | |
IL_000d: ldarg.0 | |
IL_000e: ldc.i4.3 | |
IL_000f: ldc.i4.4 | |
IL_0010: newobj instance void ConsoleApp2.Point2::.ctor(int32, int32) | |
IL_0015: stfld valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2 | |
IL_001a: ldarg.0 | |
IL_001b: call instance void [System.Runtime]System.Object::.ctor() | |
IL_0020: ret | |
} // end of method Foo1::.ctor | |
} // end of class ConsoleApp2.Foo1 | |
.class public auto ansi beforefieldinit ConsoleApp2.Foo2 | |
extends [System.Runtime]System.Object | |
{ | |
// Fields | |
.field private initonly valuetype ConsoleApp2.Point1 _point1 | |
.field private initonly valuetype ConsoleApp2.Point2 _point2 | |
// Methods | |
.method public hidebysig | |
instance int32 Struct () cil managed | |
{ | |
// Method begins at RVA 0x20e4 | |
// Code size 30 (0x1e) | |
.maxstack 2 | |
.locals init ( | |
[0] valuetype ConsoleApp2.Point1 | |
) | |
IL_0000: ldarg.0 | |
IL_0001: ldfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1 | |
IL_0006: stloc.0 | |
IL_0007: ldloca.s 0 | |
IL_0009: call instance int32 ConsoleApp2.Point1::get_X() | |
IL_000e: ldarg.0 | |
IL_000f: ldfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1 | |
IL_0014: stloc.0 | |
IL_0015: ldloca.s 0 | |
IL_0017: call instance int32 ConsoleApp2.Point1::get_Y() | |
IL_001c: add | |
IL_001d: ret | |
} // end of method Foo2::Struct | |
.method public hidebysig | |
instance int32 ReadOnlyStruct () cil managed | |
{ | |
// Method begins at RVA 0x210e | |
// Code size 24 (0x18) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2 | |
IL_0006: call instance int32 ConsoleApp2.Point2::get_X() | |
IL_000b: ldarg.0 | |
IL_000c: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2 | |
IL_0011: call instance int32 ConsoleApp2.Point2::get_Y() | |
IL_0016: add | |
IL_0017: ret | |
} // end of method Foo2::ReadOnlyStruct | |
.method public hidebysig specialname rtspecialname | |
instance void .ctor () cil managed | |
{ | |
// Method begins at RVA 0x2127 | |
// Code size 33 (0x21) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ldc.i4.1 | |
IL_0002: ldc.i4.2 | |
IL_0003: newobj instance void ConsoleApp2.Point1::.ctor(int32, int32) | |
IL_0008: stfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1 | |
IL_000d: ldarg.0 | |
IL_000e: ldc.i4.3 | |
IL_000f: ldc.i4.4 | |
IL_0010: newobj instance void ConsoleApp2.Point2::.ctor(int32, int32) | |
IL_0015: stfld valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2 | |
IL_001a: ldarg.0 | |
IL_001b: call instance void [System.Runtime]System.Object::.ctor() | |
IL_0020: ret | |
} // end of method Foo2::.ctor | |
} // end of class ConsoleApp2.Foo2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Assembly listing for method ConsoleApp2.Foo1:Struct():int:this | |
; Emitting BLENDED_CODE for X64 CPU with AVX | |
; optimized code | |
; rsp based frame | |
; partially interruptible | |
; Final local variable assignments | |
; | |
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd | |
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi | |
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi | |
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax | |
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] | |
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax | |
; | |
; Lcl frame size = 0 | |
G_M35460_IG01: | |
G_M35460_IG02: | |
488D4708 lea rax, bword ptr [rdi+8] | |
488BF8 mov rdi, rax | |
8B3F mov edi, dword ptr [rdi] | |
037804 add edi, dword ptr [rax+4] | |
8BC7 mov eax, edi | |
G_M35460_IG03: | |
C3 ret | |
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo1:Struct():int:this | |
; ============================================================ | |
; Assembly listing for method ConsoleApp2.Foo1:ReadOnlyStruct():int:this | |
; Emitting BLENDED_CODE for X64 CPU with AVX | |
; optimized code | |
; rsp based frame | |
; partially interruptible | |
; Final local variable assignments | |
; | |
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd | |
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi | |
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi | |
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax | |
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] | |
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax | |
; | |
; Lcl frame size = 0 | |
G_M43334_IG01: | |
G_M43334_IG02: | |
488D4710 lea rax, bword ptr [rdi+16] | |
488BF8 mov rdi, rax | |
8B3F mov edi, dword ptr [rdi] | |
037804 add edi, dword ptr [rax+4] | |
8BC7 mov eax, edi | |
G_M43334_IG03: | |
C3 ret | |
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo1:ReadOnlyStruct():int:this | |
; ============================================================ | |
; Assembly listing for method ConsoleApp2.Foo2:Struct():int:this | |
; Emitting BLENDED_CODE for X64 CPU with AVX | |
; optimized code | |
; rsp based frame | |
; partially interruptible | |
; Final local variable assignments | |
; | |
; V00 this [V00,T02] ( 8, 8 ) ref -> rdi this class-hnd | |
;* V01 loc0 [V01 ] ( 0, 0 ) struct ( 8) zero-ref ld-addr-op | |
; V02 tmp0 [V02,T08] ( 2, 4 ) int -> rdi | |
; V03 tmp1 [V03,T06] ( 5, 5 ) int -> rdi V01.<X>k__BackingField(offs=0x00) P-INDEP | |
; V04 tmp2 [V04,T07] ( 5, 5 ) int -> rax V01.<Y>k__BackingField(offs=0x04) P-INDEP | |
; V05 tmp3 [V05,T00] ( 12, 24 ) byref -> rax | |
; V06 tmp4 [V06,T03] ( 4, 8 ) byref -> [rsp+0x00] | |
;# V07 OutArgs [V07 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] | |
; V08 cse0 [V08,T01] ( 11, 11 ) int -> rdi | |
; V09 cse1 [V09,T04] ( 8, 8 ) int -> rax | |
; V10 cse2 [V10,T05] ( 5, 5 ) byref -> rax | |
; | |
; Lcl frame size = 8 | |
G_M35363_IG01: | |
50 push rax | |
G_M35363_IG02: | |
488D4708 lea rax, bword ptr [rdi+8] | |
8B38 mov edi, dword ptr [rax] ; copy to registers | |
8B4004 mov eax, dword ptr [rax+4] | |
03C7 add eax, edi | |
G_M35363_IG03: | |
4883C408 add rsp, 8 | |
C3 ret | |
; Total bytes of code 17, prolog size 1 for method ConsoleApp2.Foo2:Struct():int:this | |
; ============================================================ | |
; Assembly listing for method ConsoleApp2.Foo2:ReadOnlyStruct():int:this | |
; Emitting BLENDED_CODE for X64 CPU with AVX | |
; o7 | |
ptimized code | |
; rsp based frame | |
; partially interruptible | |
; Final local variable assignments | |
; | |
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd | |
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi | |
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi | |
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax | |
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] | |
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax | |
; | |
; Lcl frame size = 0 | |
G_M43497_IG01: | |
G_M43497_IG02: | |
488D4710 lea rax, bword ptr [rdi+16] | |
488BF8 mov rdi, rax | |
8B3F mov edi, dword ptr [rdi] | |
037804 add edi, dword ptr [rax+4] | |
8BC7 mov eax, edi | |
G_M43497_IG03: | |
C3 ret | |
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo2:ReadOnlyStruct():int:this | |
; ============================================================ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Discussion
Here it is not about performance, but on value-type semantics and copy-behavior.
Non-readonly field
The produced code is straight-forward, the compiler can assume a mutable struct.
Note: in the IL there is
ldflda
Readonly field
Readonly struct
The compiler knows a immutable struct, hence can produce optimized code with
ldflda
.Normal / Mutable struct
The compiler must assume that the struct is mutated, but this is not allowed by the
readonly
modifier for the field. Hence the compiler needs to make a copy and useldfld
. The produced code is equivalent to: