[{"data":1,"prerenderedAt":1183},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-value-vs-reference-types-boxing":3},{"id":4,"title":5,"body":6,"description":1168,"difficulty":1169,"extension":1170,"framework":1171,"frameworkSlug":1172,"meta":1173,"navigation":96,"order":69,"path":1174,"qaPath":1175,"seo":1176,"stem":1177,"subtopic":1178,"topic":1179,"topicSlug":1180,"updated":1181,"__hash__":1182},"blog\u002Fblog\u002Fdotnet-value-vs-reference-types-boxing.md","Value Types vs Reference Types in C#",{"type":7,"value":8,"toc":1150},"minimark",[9,14,35,39,50,128,131,135,142,210,279,285,289,303,327,330,352,355,404,423,427,438,532,594,611,614,648,652,659,667,696,701,708,750,755,762,800,812,816,833,842,857,915,927,974,978,981,1035,1053,1082,1099,1103,1146],[10,11,13],"h2",{"id":12},"why-value-vs-reference-types-trips-people-up-in-interviews","Why value vs reference types trips people up in interviews",[15,16,17,18,22,23,26,27,30,31,34],"p",{},"Every C# developer knows the words \"value type\" and \"reference type,\" but the\ninterviews probe understanding rather than vocabulary. When does a value type live\non the heap? What actually happens when you box an int? Why does passing a ",[19,20,21],"code",{},"List\u003CT>","\nto a method let the method mutate it but not replace it? What is a ",[19,24,25],{},"ref struct"," and\nwhy can't you put ",[19,28,29],{},"Span\u003CT>"," in an ",[19,32,33],{},"async"," method? These are the questions that\ndistinguish developers who use C# from those who understand its object model.",[10,36,38],{"id":37},"the-fundamental-distinction-copy-semantics-vs-shared-identity","The fundamental distinction — copy semantics vs shared identity",[15,40,41,45,46,49],{},[42,43,44],"strong",{},"Value types"," carry their data directly. Assigning them copies the data.\n",[42,47,48],{},"Reference types"," store a reference (pointer) to data on the heap. Assigning them\ncopies the reference — both variables now point to the same object.",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F Value type: copy on assign\nint a = 10;\nint b = a;   \u002F\u002F b is an independent copy\nb = 99;\nConsole.WriteLine(a); \u002F\u002F 10 — unchanged\n\n\u002F\u002F Reference type: shared reference on assign\nvar list1 = new List\u003Cint> { 1, 2, 3 };\nvar list2 = list1;   \u002F\u002F list2 points to the SAME list object\nlist2.Add(4);\nConsole.WriteLine(list1.Count); \u002F\u002F 4 — list1 sees the change\n","csharp","",[19,58,59,67,73,79,85,91,98,104,110,116,122],{"__ignoreMap":56},[60,61,64],"span",{"class":62,"line":63},"line",1,[60,65,66],{},"\u002F\u002F Value type: copy on assign\n",[60,68,70],{"class":62,"line":69},2,[60,71,72],{},"int a = 10;\n",[60,74,76],{"class":62,"line":75},3,[60,77,78],{},"int b = a;   \u002F\u002F b is an independent copy\n",[60,80,82],{"class":62,"line":81},4,[60,83,84],{},"b = 99;\n",[60,86,88],{"class":62,"line":87},5,[60,89,90],{},"Console.WriteLine(a); \u002F\u002F 10 — unchanged\n",[60,92,94],{"class":62,"line":93},6,[60,95,97],{"emptyLinePlaceholder":96},true,"\n",[60,99,101],{"class":62,"line":100},7,[60,102,103],{},"\u002F\u002F Reference type: shared reference on assign\n",[60,105,107],{"class":62,"line":106},8,[60,108,109],{},"var list1 = new List\u003Cint> { 1, 2, 3 };\n",[60,111,113],{"class":62,"line":112},9,[60,114,115],{},"var list2 = list1;   \u002F\u002F list2 points to the SAME list object\n",[60,117,119],{"class":62,"line":118},10,[60,120,121],{},"list2.Add(4);\n",[60,123,125],{"class":62,"line":124},11,[60,126,127],{},"Console.WriteLine(list1.Count); \u002F\u002F 4 — list1 sees the change\n",[15,129,130],{},"This single distinction — copy vs shared — explains most of the behaviour differences\nthat come up in interviews.",[10,132,134],{"id":133},"where-are-they-stored-the-real-answer","Where are they stored? The real answer",[15,136,137,138,141],{},"The textbook answer — \"value types on the stack, reference types on the heap\" — is an\noversimplification that causes confusion. The correct answer is: ",[42,139,140],{},"location depends on\ncontext, not type category",".",[143,144,145,158],"table",{},[146,147,148],"thead",{},[149,150,151,155],"tr",{},[152,153,154],"th",{},"Declared as",[152,156,157],{},"Where the data lives",[159,160,161,170,178,186,194,202],"tbody",{},[149,162,163,167],{},[164,165,166],"td",{},"Local variable of a value type",[164,168,169],{},"Stack (method frame)",[149,171,172,175],{},[164,173,174],{},"Local variable of a reference type",[164,176,177],{},"Stack holds the reference; object is on the heap",[149,179,180,183],{},[164,181,182],{},"Field of a class (value type)",[164,184,185],{},"Heap — embedded inside the class object",[149,187,188,191],{},[164,189,190],{},"Field of a class (reference type)",[164,192,193],{},"Heap — reference stored in the class object; target also on heap",[149,195,196,199],{},[164,197,198],{},"Field of a struct (any type)",[164,200,201],{},"Wherever the struct lives (stack or heap)",[149,203,204,207],{},[164,205,206],{},"Array element",[164,208,209],{},"Heap — array is always a reference type; elements are embedded",[51,211,213],{"className":53,"code":212,"language":55,"meta":56,"style":56},"struct Point { public int X, Y; }  \u002F\u002F value type\n\nclass Circle\n{\n    public Point Center;  \u002F\u002F Point is a value type, but it lives on the heap\n}                          \u002F\u002F inside the Circle object\n\nvoid Method()\n{\n    Point local = new Point { X = 1, Y = 2 }; \u002F\u002F on the stack — local var\n    Circle c = new Circle();                    \u002F\u002F Circle object on the heap\n    c.Center.X = 5;                             \u002F\u002F modifying heap-embedded Point\n}\n",[19,214,215,220,224,229,234,239,244,248,253,257,262,267,273],{"__ignoreMap":56},[60,216,217],{"class":62,"line":63},[60,218,219],{},"struct Point { public int X, Y; }  \u002F\u002F value type\n",[60,221,222],{"class":62,"line":69},[60,223,97],{"emptyLinePlaceholder":96},[60,225,226],{"class":62,"line":75},[60,227,228],{},"class Circle\n",[60,230,231],{"class":62,"line":81},[60,232,233],{},"{\n",[60,235,236],{"class":62,"line":87},[60,237,238],{},"    public Point Center;  \u002F\u002F Point is a value type, but it lives on the heap\n",[60,240,241],{"class":62,"line":93},[60,242,243],{},"}                          \u002F\u002F inside the Circle object\n",[60,245,246],{"class":62,"line":100},[60,247,97],{"emptyLinePlaceholder":96},[60,249,250],{"class":62,"line":106},[60,251,252],{},"void Method()\n",[60,254,255],{"class":62,"line":112},[60,256,233],{},[60,258,259],{"class":62,"line":118},[60,260,261],{},"    Point local = new Point { X = 1, Y = 2 }; \u002F\u002F on the stack — local var\n",[60,263,264],{"class":62,"line":124},[60,265,266],{},"    Circle c = new Circle();                    \u002F\u002F Circle object on the heap\n",[60,268,270],{"class":62,"line":269},12,[60,271,272],{},"    c.Center.X = 5;                             \u002F\u002F modifying heap-embedded Point\n",[60,274,276],{"class":62,"line":275},13,[60,277,278],{},"}\n",[15,280,281,284],{},[42,282,283],{},"Rule of thumb",": Ask \"what is the container?\" not \"what is the type?\" If the\ncontainer is a class object, its fields are on the heap regardless of their type.\nIf the container is a method frame, local value types are on the stack.",[10,286,288],{"id":287},"boxing-and-unboxing-the-hidden-performance-tax","Boxing and unboxing — the hidden performance tax",[15,290,291,294,295,298,299,302],{},[42,292,293],{},"Boxing"," copies a value type into a heap-allocated ",[19,296,297],{},"System.Object"," wrapper.\n",[42,300,301],{},"Unboxing"," extracts it back. Both are implicit in the C# syntax but have real costs.",[51,304,306],{"className":53,"code":305,"language":55,"meta":56,"style":56},"int x = 42;\nobject boxed = x;  \u002F\u002F BOXING: heap allocation + copy of x's data\n\nint unboxed = (int)boxed; \u002F\u002F UNBOXING: type check + copy back\n",[19,307,308,313,318,322],{"__ignoreMap":56},[60,309,310],{"class":62,"line":63},[60,311,312],{},"int x = 42;\n",[60,314,315],{"class":62,"line":69},[60,316,317],{},"object boxed = x;  \u002F\u002F BOXING: heap allocation + copy of x's data\n",[60,319,320],{"class":62,"line":75},[60,321,97],{"emptyLinePlaceholder":96},[60,323,324],{"class":62,"line":81},[60,325,326],{},"int unboxed = (int)boxed; \u002F\u002F UNBOXING: type check + copy back\n",[15,328,329],{},"Boxing costs:",[331,332,333,340,346],"ol",{},[334,335,336,339],"li",{},[42,337,338],{},"A heap allocation"," — the GC must find space on the managed heap.",[334,341,342,345],{},[42,343,344],{},"A memory copy"," — the value's bytes are copied into the new heap object.",[334,347,348,351],{},[42,349,350],{},"GC pressure"," — every boxed value is a small heap object the GC must eventually collect.",[15,353,354],{},"The most common source of hidden boxing in older .NET code is non-generic collections:",[51,356,358],{"className":53,"code":357,"language":55,"meta":56,"style":56},"\u002F\u002F ArrayList — every Add boxes value types:\nvar al = new ArrayList();\nal.Add(42);            \u002F\u002F boxes int → object\nal.Add(true);          \u002F\u002F boxes bool → object\nint v = (int)al[0];    \u002F\u002F unboxes + cast\n\n\u002F\u002F Fix: use generic List\u003CT> — no boxing:\nvar list = new List\u003Cint>();\nlist.Add(42);           \u002F\u002F int stored directly as int\n",[19,359,360,365,370,375,380,385,389,394,399],{"__ignoreMap":56},[60,361,362],{"class":62,"line":63},[60,363,364],{},"\u002F\u002F ArrayList — every Add boxes value types:\n",[60,366,367],{"class":62,"line":69},[60,368,369],{},"var al = new ArrayList();\n",[60,371,372],{"class":62,"line":75},[60,373,374],{},"al.Add(42);            \u002F\u002F boxes int → object\n",[60,376,377],{"class":62,"line":81},[60,378,379],{},"al.Add(true);          \u002F\u002F boxes bool → object\n",[60,381,382],{"class":62,"line":87},[60,383,384],{},"int v = (int)al[0];    \u002F\u002F unboxes + cast\n",[60,386,387],{"class":62,"line":93},[60,388,97],{"emptyLinePlaceholder":96},[60,390,391],{"class":62,"line":100},[60,392,393],{},"\u002F\u002F Fix: use generic List\u003CT> — no boxing:\n",[60,395,396],{"class":62,"line":106},[60,397,398],{},"var list = new List\u003Cint>();\n",[60,400,401],{"class":62,"line":112},[60,402,403],{},"list.Add(42);           \u002F\u002F int stored directly as int\n",[15,405,406,407,410,411,414,415,418,419,422],{},"Other boxing traps: passing value types to methods that accept ",[19,408,409],{},"object",", string\ninterpolation with ",[19,412,413],{},"$\"{someStruct}\""," (though this is fixed for common types in modern .NET),\nand interface calls on unboxed value types (calling an interface method on a struct\nboxes the struct first, unless the reference is typed as ",[19,416,417],{},"T"," where ",[19,420,421],{},"T : IInterface",").",[10,424,426],{"id":425},"struct-vs-class-when-to-choose-which","Struct vs class — when to choose which",[15,428,429,430,433,434,437],{},"Both ",[19,431,432],{},"struct"," and ",[19,435,436],{},"class"," can have fields, properties, methods, and implement\ninterfaces. The key differences:",[143,439,440,451],{},[146,441,442],{},[149,443,444,447,449],{},[152,445,446],{},"Concern",[152,448,432],{},[152,450,436],{},[159,452,453,464,475,486,500,515],{},[149,454,455,458,461],{},[164,456,457],{},"Type category",[164,459,460],{},"Value type",[164,462,463],{},"Reference type",[149,465,466,469,472],{},[164,467,468],{},"Default value",[164,470,471],{},"Zero-initialised (no null)",[164,473,474],{},"null",[149,476,477,480,483],{},[164,478,479],{},"Inheritance",[164,481,482],{},"Cannot inherit from another struct\u002Fclass; can implement interfaces",[164,484,485],{},"Full inheritance hierarchy",[149,487,488,494,497],{},[164,489,490,493],{},[19,491,492],{},"new"," in local scope",[164,495,496],{},"Stack-allocated (no GC pressure)",[164,498,499],{},"Heap-allocated",[149,501,502,505,512],{},[164,503,504],{},"Equality default",[164,506,507,508,511],{},"Structural (member-wise via ",[19,509,510],{},"ValueType.Equals",")",[164,513,514],{},"Identity (same object)",[149,516,517,520,529],{},[164,518,519],{},"Nullable",[164,521,522,525,526],{},[19,523,524],{},"Point?"," is ",[19,527,528],{},"Nullable\u003CPoint>",[164,530,531],{},"All reference types are already nullable",[51,533,535],{"className":53,"code":534,"language":55,"meta":56,"style":56},"\u002F\u002F Good struct: small, immutable, value semantics\nreadonly struct Vector2\n{\n    public readonly float X, Y;\n    public Vector2(float x, float y) => (X, Y) = (x, y);\n    public float Length => MathF.Sqrt(X * X + Y * Y);\n    public static Vector2 operator +(Vector2 a, Vector2 b) => new(a.X + b.X, a.Y + b.Y);\n}\n\n\u002F\u002F Bad struct candidate: too large, mutable, identity matters\n\u002F\u002F struct Customer { string Name; List\u003COrder> Orders; \u002F* ... lots more *\u002F }\n\u002F\u002F → should be a class\n",[19,536,537,542,547,551,556,561,566,571,575,579,584,589],{"__ignoreMap":56},[60,538,539],{"class":62,"line":63},[60,540,541],{},"\u002F\u002F Good struct: small, immutable, value semantics\n",[60,543,544],{"class":62,"line":69},[60,545,546],{},"readonly struct Vector2\n",[60,548,549],{"class":62,"line":75},[60,550,233],{},[60,552,553],{"class":62,"line":81},[60,554,555],{},"    public readonly float X, Y;\n",[60,557,558],{"class":62,"line":87},[60,559,560],{},"    public Vector2(float x, float y) => (X, Y) = (x, y);\n",[60,562,563],{"class":62,"line":93},[60,564,565],{},"    public float Length => MathF.Sqrt(X * X + Y * Y);\n",[60,567,568],{"class":62,"line":100},[60,569,570],{},"    public static Vector2 operator +(Vector2 a, Vector2 b) => new(a.X + b.X, a.Y + b.Y);\n",[60,572,573],{"class":62,"line":106},[60,574,278],{},[60,576,577],{"class":62,"line":112},[60,578,97],{"emptyLinePlaceholder":96},[60,580,581],{"class":62,"line":118},[60,582,583],{},"\u002F\u002F Bad struct candidate: too large, mutable, identity matters\n",[60,585,586],{"class":62,"line":124},[60,587,588],{},"\u002F\u002F struct Customer { string Name; List\u003COrder> Orders; \u002F* ... lots more *\u002F }\n",[60,590,591],{"class":62,"line":269},[60,592,593],{},"\u002F\u002F → should be a class\n",[15,595,596,597,600,601,600,604,600,607,610],{},"The .NET framework's guidelines: prefer struct for types smaller than ~16 bytes, that\nare logically a single value (like a number or coordinate), are immutable, and are not\nfrequently boxed. ",[19,598,599],{},"int",", ",[19,602,603],{},"DateTime",[19,605,606],{},"Guid",[19,608,609],{},"System.Numerics.Vector3"," all meet these\ncriteria.",[15,612,613],{},"Mutable structs are a common source of bugs because assignments copy silently:",[51,615,617],{"className":53,"code":616,"language":55,"meta":56,"style":56},"struct Mutable { public int X; }\n\nMutable m = new Mutable { X = 1 };\nvar list = new List\u003CMutable> { m };\nlist[0].X = 99; \u002F\u002F COMPILE ERROR — indexer returns a copy; you'd modify the copy\n\u002F\u002F Fix: use a class, or replace the entire struct: list[0] = new Mutable { X = 99 };\n",[19,618,619,624,628,633,638,643],{"__ignoreMap":56},[60,620,621],{"class":62,"line":63},[60,622,623],{},"struct Mutable { public int X; }\n",[60,625,626],{"class":62,"line":69},[60,627,97],{"emptyLinePlaceholder":96},[60,629,630],{"class":62,"line":75},[60,631,632],{},"Mutable m = new Mutable { X = 1 };\n",[60,634,635],{"class":62,"line":81},[60,636,637],{},"var list = new List\u003CMutable> { m };\n",[60,639,640],{"class":62,"line":87},[60,641,642],{},"list[0].X = 99; \u002F\u002F COMPILE ERROR — indexer returns a copy; you'd modify the copy\n",[60,644,645],{"class":62,"line":93},[60,646,647],{},"\u002F\u002F Fix: use a class, or replace the entire struct: list[0] = new Mutable { X = 99 };\n",[10,649,651],{"id":650},"reference-parameters-ref-out-in","Reference parameters — ref, out, in",[15,653,654,655,658],{},"By default C# passes ",[42,656,657],{},"all"," arguments by value — a copy of the argument. For value\ntypes, that's the data. For reference types, that's the reference. The three modifiers\nchange this behaviour.",[660,661,663,666],"h3",{"id":662},"ref-readwrite-alias",[19,664,665],{},"ref"," — read\u002Fwrite alias",[51,668,670],{"className":53,"code":669,"language":55,"meta":56,"style":56},"void Increment(ref int n) => n++;\n\nint x = 5;\nIncrement(ref x);    \u002F\u002F must use 'ref' at call site\nConsole.WriteLine(x); \u002F\u002F 6 — x was modified through the alias\n",[19,671,672,677,681,686,691],{"__ignoreMap":56},[60,673,674],{"class":62,"line":63},[60,675,676],{},"void Increment(ref int n) => n++;\n",[60,678,679],{"class":62,"line":69},[60,680,97],{"emptyLinePlaceholder":96},[60,682,683],{"class":62,"line":75},[60,684,685],{},"int x = 5;\n",[60,687,688],{"class":62,"line":81},[60,689,690],{},"Increment(ref x);    \u002F\u002F must use 'ref' at call site\n",[60,692,693],{"class":62,"line":87},[60,694,695],{},"Console.WriteLine(x); \u002F\u002F 6 — x was modified through the alias\n",[15,697,698,700],{},[19,699,665],{}," is useful for avoiding large struct copies in performance-sensitive code and for\nmethods that need to replace the caller's reference variable.",[660,702,704,707],{"id":703},"out-initialise-before-returning",[19,705,706],{},"out"," — initialise before returning",[51,709,711],{"className":53,"code":710,"language":55,"meta":56,"style":56},"bool TryParse(string s, out int result)\n{\n    result = 0; \u002F\u002F must assign before returning true or false\n    return int.TryParse(s, out result);\n}\n\nif (TryParse(\"42\", out int n))  \u002F\u002F n declared inline (C# 7+)\n    Console.WriteLine(n); \u002F\u002F 42\n",[19,712,713,718,722,727,732,736,740,745],{"__ignoreMap":56},[60,714,715],{"class":62,"line":63},[60,716,717],{},"bool TryParse(string s, out int result)\n",[60,719,720],{"class":62,"line":69},[60,721,233],{},[60,723,724],{"class":62,"line":75},[60,725,726],{},"    result = 0; \u002F\u002F must assign before returning true or false\n",[60,728,729],{"class":62,"line":81},[60,730,731],{},"    return int.TryParse(s, out result);\n",[60,733,734],{"class":62,"line":87},[60,735,278],{},[60,737,738],{"class":62,"line":93},[60,739,97],{"emptyLinePlaceholder":96},[60,741,742],{"class":62,"line":100},[60,743,744],{},"if (TryParse(\"42\", out int n))  \u002F\u002F n declared inline (C# 7+)\n",[60,746,747],{"class":62,"line":106},[60,748,749],{},"    Console.WriteLine(n); \u002F\u002F 42\n",[15,751,752,754],{},[19,753,706],{}," is the standard pattern for multi-return values and Try-style methods. The\nvariable need not be initialised before the call, but the method must assign it before\nreturning.",[660,756,758,761],{"id":757},"in-read-only-reference",[19,759,760],{},"in"," — read-only reference",[51,763,765],{"className":53,"code":764,"language":55,"meta":56,"style":56},"readonly struct BigMatrix { \u002F* 64 bytes *\u002F }\n\n\u002F\u002F Without 'in': 64-byte copy on every call\nvoid Compute(BigMatrix m) { \u002F* ... *\u002F }\n\n\u002F\u002F With 'in': reference, no copy, callee cannot mutate\nvoid Compute(in BigMatrix m) { \u002F* ... *\u002F }\n",[19,766,767,772,776,781,786,790,795],{"__ignoreMap":56},[60,768,769],{"class":62,"line":63},[60,770,771],{},"readonly struct BigMatrix { \u002F* 64 bytes *\u002F }\n",[60,773,774],{"class":62,"line":69},[60,775,97],{"emptyLinePlaceholder":96},[60,777,778],{"class":62,"line":75},[60,779,780],{},"\u002F\u002F Without 'in': 64-byte copy on every call\n",[60,782,783],{"class":62,"line":81},[60,784,785],{},"void Compute(BigMatrix m) { \u002F* ... *\u002F }\n",[60,787,788],{"class":62,"line":87},[60,789,97],{"emptyLinePlaceholder":96},[60,791,792],{"class":62,"line":93},[60,793,794],{},"\u002F\u002F With 'in': reference, no copy, callee cannot mutate\n",[60,796,797],{"class":62,"line":100},[60,798,799],{},"void Compute(in BigMatrix m) { \u002F* ... *\u002F }\n",[15,801,802,804,805,808,809,811],{},[19,803,760],{}," is an optimisation hint for large readonly structs. The compiler enforces that\nthe callee cannot write to ",[19,806,807],{},"m",". For small structs (≤pointer size), ",[19,810,760],{}," may actually be\nslower because dereference overhead exceeds copy cost — benchmark first.",[10,813,815],{"id":814},"ref-struct-and-spant","ref struct and Span\u003CT>",[15,817,818,819,821,822,825,826,829,830,832],{},"A ",[19,820,25],{}," is a struct that is ",[42,823,824],{},"forced to live on the stack",". The compiler\nprevents boxing, storing as a class field, or using it across ",[19,827,828],{},"await"," or lambda\nboundaries. This constraint enables a powerful guarantee: a ",[19,831,25],{}," will always\noutlive its use within the same stack frame.",[15,834,835,836,838,839,841],{},"The primary ",[19,837,25],{}," in the BCL is ",[19,840,29],{}," — a type-safe, bounds-checked view\nover contiguous memory that can point to:",[843,844,845,848,854],"ul",{},[334,846,847],{},"A managed array segment",[334,849,850,851,511],{},"Stack-allocated memory (",[19,852,853],{},"stackalloc",[334,855,856],{},"Native (unmanaged) memory",[51,858,860],{"className":53,"code":859,"language":55,"meta":56,"style":56},"\u002F\u002F Slice an array without allocating a new array:\nbyte[] data = GetData();\nSpan\u003Cbyte> header = data.AsSpan(0, 8);   \u002F\u002F zero-copy view of first 8 bytes\n\n\u002F\u002F Parse a substring without allocating a string:\nReadOnlySpan\u003Cchar> dateStr = \"2026-06-22\";\nint year = int.Parse(dateStr.Slice(0, 4)); \u002F\u002F \"2026\" — no heap allocation\n\n\u002F\u002F Stack-allocated buffer — zero GC pressure:\nSpan\u003Cbyte> buffer = stackalloc byte[512];\nFillBuffer(buffer);\n",[19,861,862,867,872,877,881,886,891,896,900,905,910],{"__ignoreMap":56},[60,863,864],{"class":62,"line":63},[60,865,866],{},"\u002F\u002F Slice an array without allocating a new array:\n",[60,868,869],{"class":62,"line":69},[60,870,871],{},"byte[] data = GetData();\n",[60,873,874],{"class":62,"line":75},[60,875,876],{},"Span\u003Cbyte> header = data.AsSpan(0, 8);   \u002F\u002F zero-copy view of first 8 bytes\n",[60,878,879],{"class":62,"line":81},[60,880,97],{"emptyLinePlaceholder":96},[60,882,883],{"class":62,"line":87},[60,884,885],{},"\u002F\u002F Parse a substring without allocating a string:\n",[60,887,888],{"class":62,"line":93},[60,889,890],{},"ReadOnlySpan\u003Cchar> dateStr = \"2026-06-22\";\n",[60,892,893],{"class":62,"line":100},[60,894,895],{},"int year = int.Parse(dateStr.Slice(0, 4)); \u002F\u002F \"2026\" — no heap allocation\n",[60,897,898],{"class":62,"line":106},[60,899,97],{"emptyLinePlaceholder":96},[60,901,902],{"class":62,"line":112},[60,903,904],{},"\u002F\u002F Stack-allocated buffer — zero GC pressure:\n",[60,906,907],{"class":62,"line":118},[60,908,909],{},"Span\u003Cbyte> buffer = stackalloc byte[512];\n",[60,911,912],{"class":62,"line":124},[60,913,914],{},"FillBuffer(buffer);\n",[15,916,917,918,920,921,923,924,141],{},"The stack-only constraint is why ",[19,919,29],{}," cannot be used in ",[19,922,33],{}," methods or stored\nin class fields. When you need a heap-storable equivalent, use ",[19,925,926],{},"Memory\u003CT>",[51,928,930],{"className":53,"code":929,"language":55,"meta":56,"style":56},"\u002F\u002F Memory\u003CT> — heap-storable alternative:\nprivate Memory\u003Cbyte> _buffer = new byte[512]; \u002F\u002F can be a class field, used across awaits\n\nasync Task ProcessAsync()\n{\n    var slice = _buffer.Slice(0, 10);\n    await SomeAsyncOperation(slice); \u002F\u002F fine — Memory\u003CT> is not a ref struct\n    Span\u003Cbyte> span = slice.Span;    \u002F\u002F get Span\u003CT> only when in a synchronous context\n}\n",[19,931,932,937,942,946,951,955,960,965,970],{"__ignoreMap":56},[60,933,934],{"class":62,"line":63},[60,935,936],{},"\u002F\u002F Memory\u003CT> — heap-storable alternative:\n",[60,938,939],{"class":62,"line":69},[60,940,941],{},"private Memory\u003Cbyte> _buffer = new byte[512]; \u002F\u002F can be a class field, used across awaits\n",[60,943,944],{"class":62,"line":75},[60,945,97],{"emptyLinePlaceholder":96},[60,947,948],{"class":62,"line":81},[60,949,950],{},"async Task ProcessAsync()\n",[60,952,953],{"class":62,"line":87},[60,954,233],{},[60,956,957],{"class":62,"line":93},[60,958,959],{},"    var slice = _buffer.Slice(0, 10);\n",[60,961,962],{"class":62,"line":100},[60,963,964],{},"    await SomeAsyncOperation(slice); \u002F\u002F fine — Memory\u003CT> is not a ref struct\n",[60,966,967],{"class":62,"line":106},[60,968,969],{},"    Span\u003Cbyte> span = slice.Span;    \u002F\u002F get Span\u003CT> only when in a synchronous context\n",[60,971,972],{"class":62,"line":112},[60,973,278],{},[10,975,977],{"id":976},"equality-semantics","Equality semantics",[15,979,980],{},"Default equality differs between value types and reference types:",[51,982,984],{"className":53,"code":983,"language":55,"meta":56,"style":56},"\u002F\u002F Value type (struct): structural equality by default (member-wise)\nvar d1 = new DateTime(2026, 1, 1);\nvar d2 = new DateTime(2026, 1, 1);\nConsole.WriteLine(d1 == d2);      \u002F\u002F True — same data\n\n\u002F\u002F Reference type (class): identity equality by default\nvar p1 = new Person { Name = \"Alice\" };\nvar p2 = new Person { Name = \"Alice\" };\nConsole.WriteLine(p1 == p2);      \u002F\u002F False — different objects\nConsole.WriteLine(p1.Equals(p2)); \u002F\u002F False — unless Equals() overridden\n",[19,985,986,991,996,1001,1006,1010,1015,1020,1025,1030],{"__ignoreMap":56},[60,987,988],{"class":62,"line":63},[60,989,990],{},"\u002F\u002F Value type (struct): structural equality by default (member-wise)\n",[60,992,993],{"class":62,"line":69},[60,994,995],{},"var d1 = new DateTime(2026, 1, 1);\n",[60,997,998],{"class":62,"line":75},[60,999,1000],{},"var d2 = new DateTime(2026, 1, 1);\n",[60,1002,1003],{"class":62,"line":81},[60,1004,1005],{},"Console.WriteLine(d1 == d2);      \u002F\u002F True — same data\n",[60,1007,1008],{"class":62,"line":87},[60,1009,97],{"emptyLinePlaceholder":96},[60,1011,1012],{"class":62,"line":93},[60,1013,1014],{},"\u002F\u002F Reference type (class): identity equality by default\n",[60,1016,1017],{"class":62,"line":100},[60,1018,1019],{},"var p1 = new Person { Name = \"Alice\" };\n",[60,1021,1022],{"class":62,"line":106},[60,1023,1024],{},"var p2 = new Person { Name = \"Alice\" };\n",[60,1026,1027],{"class":62,"line":112},[60,1028,1029],{},"Console.WriteLine(p1 == p2);      \u002F\u002F False — different objects\n",[60,1031,1032],{"class":62,"line":118},[60,1033,1034],{},"Console.WriteLine(p1.Equals(p2)); \u002F\u002F False — unless Equals() overridden\n",[15,1036,1037,1038,433,1041,1044,1045,1048,1049,1052],{},"To give a class value-like equality, override ",[19,1039,1040],{},"Equals()",[19,1042,1043],{},"GetHashCode()",", and\noptionally overload ",[19,1046,1047],{},"==",". ",[42,1050,1051],{},"C# 9 records"," automate all of this:",[51,1054,1056],{"className":53,"code":1055,"language":55,"meta":56,"style":56},"record Person(string Name, int Age);\n\nvar a = new Person(\"Alice\", 30);\nvar b = new Person(\"Alice\", 30);\nConsole.WriteLine(a == b); \u002F\u002F True — records have structural equality\n",[19,1057,1058,1063,1067,1072,1077],{"__ignoreMap":56},[60,1059,1060],{"class":62,"line":63},[60,1061,1062],{},"record Person(string Name, int Age);\n",[60,1064,1065],{"class":62,"line":69},[60,1066,97],{"emptyLinePlaceholder":96},[60,1068,1069],{"class":62,"line":75},[60,1070,1071],{},"var a = new Person(\"Alice\", 30);\n",[60,1073,1074],{"class":62,"line":81},[60,1075,1076],{},"var b = new Person(\"Alice\", 30);\n",[60,1078,1079],{"class":62,"line":87},[60,1080,1081],{},"Console.WriteLine(a == b); \u002F\u002F True — records have structural equality\n",[15,1083,1084,1085,1088,1089,1091,1092,525,1095,1098],{},"Note: ",[19,1086,1087],{},"string"," is a reference type but overrides ",[19,1090,1047],{}," for content equality, which is\nwhy ",[19,1093,1094],{},"\"hello\" == \"hello\"",[19,1096,1097],{},"true"," even though they could theoretically be different\nobjects.",[10,1100,1102],{"id":1101},"recap","Recap",[15,1104,1105,1106,1109,1110,1112,1113,1116,1117,1120,1121,1123,1124,1127,1128,1131,1132,1134,1135,1134,1137,1139,1140,1142,1143,1145],{},"C# ",[42,1107,1108],{},"value types"," carry their data directly — assignment copies the data and each\nvariable is independent. ",[42,1111,48],{}," store a pointer to heap data —\nassignment copies the reference and mutations are shared. Value type ",[42,1114,1115],{},"local variables","\nlive on the stack; value type ",[42,1118,1119],{},"fields of a class"," live on the heap inside the object.\n",[42,1122,293],{}," converts a value type to a heap object — it costs a heap allocation and\nGC pressure; avoid it in hot paths by using generics. ",[42,1125,1126],{},"Structs"," are appropriate for\nsmall (≤16 B), immutable, value-semantics types; use ",[42,1129,1130],{},"classes"," for everything else.\nThe ",[19,1133,665],{},"\u002F",[19,1136,706],{},[19,1138,760],{}," parameter modifiers control whether a variable is passed by value\nor by reference and what mutation is allowed. ",[19,1141,29],{}," is the BCL's zero-allocation\nmemory-view primitive, implemented as a ",[19,1144,25],{}," to guarantee stack-only lifetime.",[1147,1148,1149],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":56,"searchDepth":69,"depth":69,"links":1151},[1152,1153,1154,1155,1156,1157,1165,1166,1167],{"id":12,"depth":69,"text":13},{"id":37,"depth":69,"text":38},{"id":133,"depth":69,"text":134},{"id":287,"depth":69,"text":288},{"id":425,"depth":69,"text":426},{"id":650,"depth":69,"text":651,"children":1158},[1159,1161,1163],{"id":662,"depth":75,"text":1160},"ref — read\u002Fwrite alias",{"id":703,"depth":75,"text":1162},"out — initialise before returning",{"id":757,"depth":75,"text":1164},"in — read-only reference",{"id":814,"depth":69,"text":815},{"id":976,"depth":69,"text":977},{"id":1101,"depth":69,"text":1102},"How C# splits types between the stack and the heap — copy semantics, boxing costs, struct vs class trade-offs, and the ref struct constraint that makes Span\u003CT> possible.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-value-vs-reference-types-boxing","\u002Fdotnet\u002Ffundamentals\u002Fvalue-vs-reference-types",{"title":5,"description":1168},"blog\u002Fdotnet-value-vs-reference-types-boxing","Value vs Reference Types","Fundamentals","fundamentals","2026-06-22","l0yECUSYmt5rY7B1VMFMmY01pkIKKBJzBD9mjDbYuuQ",1782244087197]