[{"data":1,"prerenderedAt":115},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Ffundamentals\u002Fvalue-vs-reference-types":3},{"page":4,"siblings":94,"blog":112},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":89,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fdotnet\u002Ffundamentals\u002Fvalue-vs-reference-types.md","Value Vs Reference Types",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,"\u002Fdotnet\u002Ffundamentals\u002Fvalue-vs-reference-types",[22,27,31,35,39,43,47,51,55,59,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"value-vs-reference-basics","easy","What is the difference between value types and reference types in C#?","**Value types** hold their data directly. **Reference types** hold a pointer (reference)\nto data stored on the managed heap. Assigning a value type copies the data; assigning\na reference type copies only the reference — both variables then point to the same object.\n\n```csharp\n\u002F\u002F Value type — copy semantics\nint a = 5;\nint b = a;   \u002F\u002F b gets a copy of 5\nb = 99;\nConsole.WriteLine(a); \u002F\u002F 5 — a is unaffected\n\n\u002F\u002F Reference type — shared reference\nvar list1 = new List\u003Cint> { 1, 2, 3 };\nvar list2 = list1;   \u002F\u002F list2 points to the SAME list\nlist2.Add(4);\nConsole.WriteLine(list1.Count); \u002F\u002F 4 — list1 sees the change\n```\n\nValue types: `int`, `double`, `bool`, `char`, `decimal`, `DateTime`, `struct`, `enum`.\nReference types: `class`, `string`, `object`, arrays, delegates, interfaces.\n`string` behaves like a value type in practice because it is **immutable** — any\n\"change\" creates a new string object.\n\n**Rule of thumb:** If you need copy-on-assign semantics and the data is small and\nshort-lived, use a value type (struct). For anything polymorphic, large, or long-lived,\nuse a class.\n",{"id":28,"difficulty":14,"q":29,"a":30},"stack-vs-heap-storage","Where are value types and reference types stored in memory?","The common interview answer — \"value types on the stack, reference types on the heap\"\n— is an oversimplification. What matters is **how the variable is declared**.\n\n```csharp\nvoid Method()\n{\n    int x = 10;             \u002F\u002F local value type → stack frame\n    object obj = new();     \u002F\u002F 'new object' → heap; reference 'obj' → stack\n}\n\nclass Wrapper\n{\n    int field = 42;         \u002F\u002F value type field inside a class → HEAP (embedded in the object)\n    string name = \"hello\";  \u002F\u002F reference type field → heap (reference stored in object, string on heap)\n}\n\n\u002F\u002F Struct stored as a field of a class lives on the heap inside the class:\nclass Circle { public System.Drawing.Point Center; } \u002F\u002F Point (struct) is on heap\n```\n\nLocal variables of value types live on the **stack** of the current method frame.\nValue type **fields inside a class** live on the **heap** as part of the object.\nReference types always allocate their data on the **heap**; the variable (reference)\nitself can be on either stack or heap depending on context.\n\n**Rule of thumb:** Think \"where is the variable declared?\" not \"what type is it?\" —\nthe location follows the container, not the type.\n",{"id":32,"difficulty":14,"q":33,"a":34},"boxing-unboxing","What is boxing and unboxing, and why is it a performance concern?","**Boxing** wraps a value type in a heap-allocated `System.Object` wrapper. **Unboxing**\nextracts the value type back out. Both operations are implicit but incur hidden costs.\n\n```csharp\nint x = 42;\nobject boxed = x;         \u002F\u002F BOXING: heap allocation + copy\nint unboxed = (int)boxed; \u002F\u002F UNBOXING: type check + copy\n\n\u002F\u002F Hidden boxing in older APIs:\nvar al = new ArrayList();\nal.Add(42);               \u002F\u002F boxes 42 to object — heap allocation!\nint v = (int)al[0];       \u002F\u002F unboxes — type check + copy\n\n\u002F\u002F Generic collections avoid boxing entirely:\nvar list = new List\u003Cint>();\nlist.Add(42);             \u002F\u002F no boxing — int stored directly\nint v2 = list[0];         \u002F\u002F no unboxing\n```\n\nBoxing costs: a **heap allocation**, a **memory copy**, and eventual **GC pressure**.\nIn a tight loop adding thousands of ints to an `ArrayList`, this adds up significantly.\nString interpolation pre-.NET 6 also caused boxing for value types passed as `object`.\nThe fix is always **generics** — `List\u003Cint>` instead of `ArrayList`, `IComparable\u003CT>`\ninstead of `IComparable`.\n\n**Rule of thumb:** Avoid boxing in hot paths. If you see `object` parameters or\nnon-generic collections in performance-sensitive code, that's a boxing smell.\n",{"id":36,"difficulty":24,"q":37,"a":38},"struct-vs-class","What is the difference between a struct and a class in C#?","A **struct** is a value type; a **class** is a reference type. The difference\ndetermines copy semantics, memory layout, inheritance rules, and GC behavior.\n\n```csharp\nstruct Point { public int X, Y; }   \u002F\u002F value type\nclass Circle { public int Radius; } \u002F\u002F reference type\n\nvar p1 = new Point { X = 1, Y = 2 };\nvar p2 = p1;   \u002F\u002F copy — p2 is independent\np2.X = 99;\nConsole.WriteLine(p1.X); \u002F\u002F 1 — unaffected\n\nvar c1 = new Circle { Radius = 5 };\nvar c2 = c1;   \u002F\u002F shared reference\nc2.Radius = 99;\nConsole.WriteLine(c1.Radius); \u002F\u002F 99 — both see the change\n```\n\nKey differences:\n| | struct | class |\n|---|---|---|\n| Type | Value type | Reference type |\n| Default | Zero-initialised, no null | null |\n| Inheritance | Cannot inherit (can implement interfaces) | Full inheritance |\n| `new` | Stack-allocated (if local) | Always heap-allocated |\n| `IDisposable` | Valid but uncommon | Common |\n\n**Rule of thumb:** Use struct for small (≤16 bytes), immutable, logically\nvalue-like data (coordinates, colours, `Guid`). Use class for everything else.\n",{"id":40,"difficulty":14,"q":41,"a":42},"when-to-use-struct","When should you use a struct instead of a class?","Prefer a **struct** when the type is small, immutable, frequently copied in bulk\n(e.g., array of coordinates), and semantically represents a single value rather\nthan an entity with identity.\n\n```csharp\n\u002F\u002F Good struct candidate: 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}\n\n\u002F\u002F Bad struct candidate: large, mutable, reference identity matters\n\u002F\u002F struct HttpClient { ... } \u002F\u002F wrong — should be class\n```\n\nThe CLR can lay out arrays of structs as contiguous memory blocks, which is\nCPU-cache-friendly — a big win for game engines and numerical code. The .NET\nframework uses structs for: `int`, `DateTime`, `Guid`, `KeyValuePair\u003CK,V>`,\n`System.Numerics.Vector3`, and `Span\u003CT>`.\n\nAvoid structs when: the type is larger than ~16 bytes (copying costs outweigh\nallocation savings), it is mutable (mutation bugs are hard to spot with copy\nsemantics), or it needs to be null (use `T?` or a class).\n\n**Rule of thumb:** Default to class. Switch to struct only when profiling shows\nGC pressure from many small, short-lived objects of that type.\n",{"id":44,"difficulty":14,"q":45,"a":46},"pass-by-value","What does \"pass by value\" mean in C#, and how does it apply to reference types?","In C#, **all arguments are passed by value by default** — a copy of the argument\nis made. For value types, the copy is the data itself. For reference types, the\ncopy is the **reference** (pointer), not the object it points to.\n\n```csharp\nvoid MutateList(List\u003Cint> list)\n{\n    list.Add(99);     \u002F\u002F mutates the shared object — caller sees this\n    list = new List\u003Cint>(); \u002F\u002F rebinds local copy of reference — caller doesn't see this\n}\n\nvar nums = new List\u003Cint> { 1, 2 };\nMutateList(nums);\nConsole.WriteLine(nums.Count); \u002F\u002F 3 — Add(99) was visible; reassignment was not\n```\n\n\"Pass by value\" for reference types means the reference is copied. Both the caller\nand callee hold separate copies of the reference that point to the same object.\nMutating the object through the callee's reference is visible to the caller.\nReassigning the callee's reference variable does not affect the caller.\n\n**Rule of thumb:** Pass by value = copy of the handle, not the object. If you need\nto replace the caller's reference itself, use `ref` or return the new value.\n",{"id":48,"difficulty":14,"q":49,"a":50},"ref-keyword","What does the `ref` keyword do when passing parameters?","The `ref` keyword passes a variable **by reference** — the method receives an alias\nto the caller's variable, not a copy. Changes to the parameter directly modify the\ncaller's variable, including reassignment.\n\n```csharp\nvoid Double(ref int value) => value *= 2;\n\nint x = 5;\nDouble(ref x);           \u002F\u002F must use 'ref' at call site too\nConsole.WriteLine(x);   \u002F\u002F 10 — x was modified in-place\n\n\u002F\u002F ref with reference types — can replace the caller's reference:\nvoid ReplaceList(ref List\u003Cint> list)\n{\n    list = new List\u003Cint> { 99 }; \u002F\u002F caller's variable now points to new list\n}\n\nvar nums = new List\u003Cint> { 1, 2 };\nReplaceList(ref nums);\nConsole.WriteLine(nums[0]); \u002F\u002F 99\n```\n\n`ref` is commonly used in high-performance code to avoid copying large structs,\nin `TryGetValue`-style patterns, and in `ref return` \u002F `ref local` to expose\nreferences into arrays or data structures without copying.\n\n**Rule of thumb:** Use `ref` when the method needs to reassign the caller's variable,\nor to avoid copying large structs in performance-critical code.\n",{"id":52,"difficulty":24,"q":53,"a":54},"out-keyword","What is the `out` parameter modifier and how does it differ from `ref`?","`out` is like `ref` — it passes by reference — but with two differences: the\nvariable does **not** need to be initialised before the call, and the method\n**must** assign it before returning.\n\n```csharp\n\u002F\u002F Classic Try-Parse pattern:\nbool success = int.TryParse(\"42\", out int result);\n\u002F\u002F 'result' was not initialised before the call — that's fine with 'out'\nConsole.WriteLine(success ? result : -1); \u002F\u002F 42\n\n\u002F\u002F Inline declaration (C# 7+):\nif (double.TryParse(\"3.14\", out double pi))\n    Console.WriteLine(pi); \u002F\u002F 3.14\n\n\u002F\u002F Discard when you don't need the out value:\nbool isValid = int.TryParse(input, out _);\n```\n\n| | `ref` | `out` |\n|---|---|---|\n| Must be initialised before call | Yes | No |\n| Must be assigned in method | No | Yes |\n| Use case | Two-way data exchange | Return multiple values |\n\n**Rule of thumb:** Use `out` for the Try-Parse \u002F Try-Get pattern where the method\nsignals success\u002Ffailure via return value and returns data via `out`. Use `ref` when\nthe method needs to read the initial value too.\n",{"id":56,"difficulty":14,"q":57,"a":58},"in-keyword","What is the `in` parameter modifier in C#?","The `in` modifier passes a variable by reference but guarantees the method will\n**not modify** it — it is a **read-only reference**. This avoids copying large\nstructs while providing compile-time safety that the callee cannot mutate.\n\n```csharp\nreadonly struct Matrix4x4 { \u002F* 64 bytes *\u002F }\n\n\u002F\u002F Without 'in': 64-byte copy on every call\nfloat Determinant(Matrix4x4 m) => \u002F* ... *\u002F 0f;\n\n\u002F\u002F With 'in': passed by reference, no copy, callee cannot mutate\nfloat Determinant(in Matrix4x4 m) => \u002F* ... *\u002F 0f;\n\nvar mat = new Matrix4x4();\nfloat det = Determinant(in mat); \u002F\u002F 'in' optional at call site for value types\n```\n\n`in` is most valuable for **large structs** in hot paths (rendering, physics,\nlinear algebra). For small structs (≤pointer size), it may actually be slower\nbecause the CPU must dereference the pointer. Benchmark before adding `in` to\nsmall structs.\n\n**Rule of thumb:** Add `in` to struct parameters larger than a pointer (8 bytes\non 64-bit) in performance-sensitive code. Leave it off for small structs and all\nreference types (reference types already pass the pointer, not the object).\n",{"id":60,"difficulty":61,"q":62,"a":63},"ref-struct","hard","What is a `ref struct` in C#?","A `ref struct` is a struct that is **guaranteed to live only on the stack**. The\nCLR enforces this at compile time — you cannot box it, store it as a class field,\nuse it as a generic type argument, or capture it in a lambda or async method.\n\n```csharp\nref struct StackOnly\n{\n    public int Value;\n}\n\n\u002F\u002F Compile-time error — cannot box a ref struct:\n\u002F\u002F object obj = new StackOnly();\n\u002F\u002F Cannot be a field of a class:\n\u002F\u002F class Wrapper { StackOnly s; }\n\u002F\u002F Cannot use in async context:\n\u002F\u002F async Task Foo() { var s = new StackOnly(); await Task.Delay(1); }\n\u002F\u002F Correct use — short-lived, stack-bound:\nvoid Process(Span\u003Cbyte> data)\n{\n    \u002F\u002F Span\u003CT> is itself a ref struct — stack-only\n    var slice = data[..10]; \u002F\u002F fine: slice is also Span\u003CT> (ref struct)\n}\n```\n\nThe primary use case is `Span\u003CT>` and `ReadOnlySpan\u003CT>` — high-performance, safe\nwrappers over contiguous memory (arrays, stack memory, unmanaged memory) that must\nnot outlive their source. `ref struct` is the mechanism that enforces this lifetime.\n\n**Rule of thumb:** `ref struct` is an advanced, performance-focused feature.\nYou'll mostly encounter it as `Span\u003CT>`. Only define your own when you need a\nstack-bound type with lifetime guarantees.\n",{"id":65,"difficulty":61,"q":66,"a":67},"span-t","What is `Span\u003CT>` and why is it a `ref struct`?","`Span\u003CT>` is a **stack-only, type-safe view over a contiguous block of memory**.\nIt can point to a managed array, a stack-allocated buffer, or unmanaged memory —\nwithout copying any data.\n\n```csharp\n\u002F\u002F Over a managed array — no copy:\nint[] arr = { 1, 2, 3, 4, 5 };\nSpan\u003Cint> span = arr.AsSpan(1, 3); \u002F\u002F points to arr[1..3] = {2, 3, 4}\nspan[0] = 99;\nConsole.WriteLine(arr[1]); \u002F\u002F 99 — same memory\n\n\u002F\u002F Stack-allocated buffer — zero heap allocation:\nSpan\u003Cbyte> buffer = stackalloc byte[256];\nbuffer.Fill(0);\n\n\u002F\u002F String parsing without allocations (ReadOnlySpan\u003Cchar>):\nReadOnlySpan\u003Cchar> line = \"2026-06-22\".AsSpan();\nint year = int.Parse(line[..4]); \u002F\u002F parses \"2026\" without allocating a substring\n```\n\n`Span\u003CT>` is a `ref struct` because it stores an interior pointer into managed\nmemory. If it were allowed on the heap (as a class field or boxed value), the GC\ncould move the pointed-to array while the span's pointer still held the old address.\nBeing stack-only ensures the span is always used within the lifetime of its source.\n\n**Rule of thumb:** Use `Span\u003CT>` \u002F `ReadOnlySpan\u003CT>` to eliminate string and array\nslice allocations in hot paths. It is the .NET replacement for `char*` \u002F `byte*` unsafe pointers.\n",{"id":69,"difficulty":24,"q":70,"a":71},"equality-value-vs-reference","How does equality work differently for value types vs reference types in C#?","By default, **value types** compare by content (structural equality); **reference\ntypes** compare by identity (same object in memory). This comes from how `Equals()`\nand `==` are implemented on `System.ValueType` vs `System.Object`.\n\n```csharp\n\u002F\u002F Value type — structural equality by default:\nvar d1 = new DateTime(2026, 1, 1);\nvar d2 = new DateTime(2026, 1, 1);\nConsole.WriteLine(d1 == d2);      \u002F\u002F True — same data\nConsole.WriteLine(d1.Equals(d2)); \u002F\u002F True\n\n\u002F\u002F Reference type — 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 is overridden)\n\n\u002F\u002F string is a reference type but overrides == for value equality:\nstring s1 = \"hello\", s2 = \"hello\";\nConsole.WriteLine(s1 == s2); \u002F\u002F True — string overrides ==\n```\n\nTo get value equality on a class, override `Equals()` and `GetHashCode()`, and\noptionally overload `==`. **C# 9 records** do this automatically.\n\n**Rule of thumb:** Value types are equal when their data is equal. Reference types\nare equal when they are the same object — override `Equals`\u002F`GetHashCode` to change\nthat, or use a `record` which auto-generates value equality.\n",{"id":73,"difficulty":14,"q":74,"a":75},"readonly-struct","What is a `readonly struct` in C# and when should you use it?","A `readonly struct` is a struct where all fields and auto-properties are implicitly\nread-only. The compiler enforces immutability: you cannot assign to any field after\nconstruction, and calling instance methods on a `readonly struct` variable does not\nrequire a defensive copy.\n\n```csharp\nreadonly struct Temperature\n{\n    public double Celsius { get; }\n    public double Fahrenheit => Celsius * 9 \u002F 5 + 32;\n\n    public Temperature(double celsius) => Celsius = celsius;\n\n    \u002F\u002F Compiler error if you try:\n    \u002F\u002F public void SetCelsius(double c) { Celsius = c; } \u002F\u002F CS1604\n}\n\n\u002F\u002F The compiler no longer emits a defensive copy when calling methods:\nreadonly Temperature t = new Temperature(100);\nConsole.WriteLine(t.Fahrenheit); \u002F\u002F 212 — no defensive copy needed\n```\n\nWithout `readonly`, calling any instance method on a `readonly` local or `in`\nparameter forces the compiler to copy the struct defensively (because the method\nmight mutate it). `readonly struct` removes that copy entirely, which matters for\nhot loops with large structs.\n\n**Rule of thumb:** Mark structs `readonly` whenever all their state is set in the\nconstructor and never mutated. Combine with `in` parameters and `ref readonly`\nreturns for maximum zero-copy performance.\n",{"id":77,"difficulty":14,"q":78,"a":79},"record-types","What are record types in C# and how do they relate to value vs reference type semantics?","**Records** (C# 9+) are a special class or struct syntax that automatically generates\nvalue-based equality, a `ToString()` implementation, and deconstruction. By default,\n`record` (without `struct`) is a **reference type** but behaves with value semantics\nfor equality.\n\n```csharp\n\u002F\u002F record class (C# 9) — reference type with value equality\nrecord 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 — value equality (generated)\nConsole.WriteLine(ReferenceEquals(a, b)); \u002F\u002F False — different objects\n\n\u002F\u002F Non-destructive mutation with 'with':\nvar older = a with { Age = 31 }; \u002F\u002F creates a new Person, copies other fields\nConsole.WriteLine(older); \u002F\u002F Person { Name = Alice, Age = 31 }\n\n\u002F\u002F record struct (C# 10) — value type with value equality\nrecord struct Point(int X, int Y);\nvar p1 = new Point(1, 2);\nvar p2 = new Point(1, 2);\nConsole.WriteLine(p1 == p2); \u002F\u002F True\n```\n\nRecords also generate a positional constructor and `Deconstruct` method from\nthe primary constructor syntax. They are ideal for DTOs, commands, events, and\nany immutable data-carrying types.\n\n**Rule of thumb:** Use `record class` for immutable reference types that need\nvalue equality (DTOs, domain events). Use `record struct` for small immutable\nvalue types. Use `class` when you need mutable state or inheritance beyond records.\n",{"id":81,"difficulty":61,"q":82,"a":83},"string-interning","What is string interning in .NET and how does it relate to reference type equality?","**String interning** is a runtime optimisation where the CLR maintains a pool of\nunique string literals. When two string literals have the same content, the CLR\nmakes them reference the same object, saving memory. This can cause surprising\nresults when comparing strings with `ReferenceEquals`.\n\n```csharp\nstring a = \"hello\";\nstring b = \"hello\";\n\u002F\u002F Compile-time literal interning: a and b are the SAME object\nConsole.WriteLine(ReferenceEquals(a, b)); \u002F\u002F True — interned\n\nstring c = new string(new[] { 'h','e','l','l','o' }); \u002F\u002F runtime-constructed\nConsole.WriteLine(ReferenceEquals(a, c)); \u002F\u002F False — different object\n\n\u002F\u002F Force interning of a runtime string:\nstring d = string.Intern(c);\nConsole.WriteLine(ReferenceEquals(a, d)); \u002F\u002F True — d is now the interned version\n\n\u002F\u002F Check if a string is already interned without adding it:\nstring? existing = string.IsInterned(\"hello\"); \u002F\u002F returns the interned ref or null\n```\n\nString interning applies automatically to compile-time literals across all\nassemblies. Runtime strings (built from `StringBuilder`, read from DB, etc.) are\nNOT interned by default. Never compare strings with `==` expecting reference\nidentity — always use `==` (which calls `string.Equals`) or `.Equals()`.\n\n**Rule of thumb:** Use `==` or `.Equals()` for string value comparison — never\n`ReferenceEquals`. Interning is a CLR detail, not a correctness tool. Only call\n`string.Intern` when you have profiled and confirmed it reduces memory for a large\nset of repeated strings.\n",15,null,{"description":11},"C# value types vs reference types — stack and heap allocation, boxing costs, struct vs class trade-offs, ref parameters, and Span\u003CT>.","dotnet\u002Ffundamentals\u002Fvalue-vs-reference-types","Value vs Reference Types","Fundamentals","fundamentals","2026-06-22","Yt8M21EN0NFY0pWsYDEUkJ_Z83QUPSeNzNCtboY58ic",[95,99,100,104,108],{"subtopic":96,"path":97,"order":98},"CLR Runtime","\u002Fdotnet\u002Ffundamentals\u002Fclr-runtime",1,{"subtopic":89,"path":20,"order":12},{"subtopic":101,"path":102,"order":103},"Generics","\u002Fdotnet\u002Ffundamentals\u002Fgenerics",3,{"subtopic":105,"path":106,"order":107},"LINQ","\u002Fdotnet\u002Ffundamentals\u002Flinq",4,{"subtopic":109,"path":110,"order":111},"Nullable Types","\u002Fdotnet\u002Ffundamentals\u002Fnullable-types",5,{"path":113,"title":114},"\u002Fblog\u002Fdotnet-value-vs-reference-types-boxing","Value Types vs Reference Types in C#",1782244117819]