[{"data":1,"prerenderedAt":1501},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-nullable-types-null-safety-csharp8":3},{"id":4,"title":5,"body":6,"description":1486,"difficulty":1487,"extension":1488,"framework":1489,"frameworkSlug":1490,"meta":1491,"navigation":200,"order":204,"path":1492,"qaPath":1493,"seo":1494,"stem":1495,"subtopic":1496,"topic":1497,"topicSlug":1498,"updated":1499,"__hash__":1500},"blog\u002Fblog\u002Fdotnet-nullable-types-null-safety-csharp8.md","Null Safety in C#: Nullable Types and Nullable Reference Types",{"type":7,"value":8,"toc":1466},"minimark",[9,14,23,27,30,115,128,132,163,275,302,307,310,369,376,380,386,496,500,506,564,568,581,697,703,707,718,802,806,833,867,874,878,885,968,972,985,1060,1069,1073,1076,1190,1194,1214,1275,1294,1298,1404,1408,1462],[10,11,13],"h2",{"id":12},"why-null-handling-comes-up-in-every-c-interview","Why null handling comes up in every C# interview",[15,16,17,18,22],"p",{},"Tony Hoare called null \"my billion-dollar mistake.\" In C#, null has evolved from a\nsimple runtime hazard (",[19,20,21],"code",{},"NullReferenceException",") into a first-class part of the type\nsystem with C# 8's nullable reference types. Interviewers probe null handling because\nit directly relates to code correctness, API clarity, and defensive programming. They\nwant to see you distinguish between \"this value is optional\" and \"this should never be\nnull,\" use the right operator for each case, and understand what C# 8's nullable\nannotations actually do at runtime.",[10,24,26],{"id":25},"two-completely-different-kinds-of-null-in-c","Two completely different kinds of null in C#",[15,28,29],{},"C# has two orthogonal null systems that are easy to confuse:",[31,32,33,55],"table",{},[34,35,36],"thead",{},[37,38,39,42,49],"tr",{},[40,41],"th",{},[40,43,44,45,48],{},"Nullable value types (",[19,46,47],{},"int?",")",[40,50,51,52,48],{},"Nullable reference types (",[19,53,54],{},"string?",[56,57,58,70,85,101],"tbody",{},[37,59,60,64,67],{},[61,62,63],"td",{},"Introduced",[61,65,66],{},"C# 2.0",[61,68,69],{},"C# 8.0",[37,71,72,75,82],{},[61,73,74],{},"Runtime effect",[61,76,77,78,81],{},"Real — ",[19,79,80],{},"Nullable\u003CT>"," struct exists",[61,83,84],{},"None — compile-time annotation only",[37,86,87,90,95],{},[61,88,89],{},"Runtime type",[61,91,92],{},[19,93,94],{},"System.Nullable\u003Cint>",[61,96,97,100],{},[19,98,99],{},"System.String"," (unchanged)",[37,102,103,106,109],{},[61,104,105],{},"Opt-in required",[61,107,108],{},"No — always available",[61,110,111,112],{},"Yes — ",[19,113,114],{},"\u003CNullable>enable\u003C\u002FNullable>",[15,116,117,118,120,121,123,124,127],{},"Understanding this distinction is crucial. ",[19,119,47],{}," is a genuine runtime type that\nchanges how the CLR represents the value. ",[19,122,54],{}," is purely a compile-time hint —\nat runtime it is indistinguishable from ",[19,125,126],{},"string",".",[10,129,131],{"id":130},"nullable-value-types-nullablet","Nullable value types — Nullable\u003CT>",[15,133,134,135,138,139,138,142,138,145,138,148,151,152,154,155,158,159,162],{},"All C# value types (",[19,136,137],{},"int",", ",[19,140,141],{},"double",[19,143,144],{},"bool",[19,146,147],{},"DateTime",[19,149,150],{},"struct",") are non-nullable\nby default — they cannot be set to null. ",[19,153,80],{}," (written as ",[19,156,157],{},"T?",") wraps a\nvalue type and adds a ",[19,160,161],{},"HasValue"," flag.",[164,165,170],"pre",{"className":166,"code":167,"language":168,"meta":169,"style":169},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F int — can only hold numeric values\nint a = 42;\n\u002F\u002F a = null; \u002F\u002F CS0037 — int cannot be null\n\n\u002F\u002F int? — can hold null OR numeric values\nint? b = 42;\nint? c = null;\n\n\u002F\u002F The two properties:\nConsole.WriteLine(b.HasValue);  \u002F\u002F True\nConsole.WriteLine(b.Value);     \u002F\u002F 42\n\nConsole.WriteLine(c.HasValue);  \u002F\u002F False\n\u002F\u002F Console.WriteLine(c.Value);  \u002F\u002F InvalidOperationException!\nConsole.WriteLine(c.GetValueOrDefault()); \u002F\u002F 0 — safe, no throw\nConsole.WriteLine(c.GetValueOrDefault(-1)); \u002F\u002F -1 — custom fallback\n","csharp","",[19,171,172,180,186,195,202,208,214,220,225,231,237,243,248,254,263,269],{"__ignoreMap":169},[173,174,177],"span",{"class":175,"line":176},"line",1,[173,178,179],{},"\u002F\u002F int — can only hold numeric values\n",[173,181,183],{"class":175,"line":182},2,[173,184,185],{},"int a = 42;\n",[173,187,189,192],{"class":175,"line":188},3,[173,190,191],{},"\u002F\u002F a = null;",[173,193,194],{}," \u002F\u002F CS0037 — int cannot be null\n",[173,196,198],{"class":175,"line":197},4,[173,199,201],{"emptyLinePlaceholder":200},true,"\n",[173,203,205],{"class":175,"line":204},5,[173,206,207],{},"\u002F\u002F int? — can hold null OR numeric values\n",[173,209,211],{"class":175,"line":210},6,[173,212,213],{},"int? b = 42;\n",[173,215,217],{"class":175,"line":216},7,[173,218,219],{},"int? c = null;\n",[173,221,223],{"class":175,"line":222},8,[173,224,201],{"emptyLinePlaceholder":200},[173,226,228],{"class":175,"line":227},9,[173,229,230],{},"\u002F\u002F The two properties:\n",[173,232,234],{"class":175,"line":233},10,[173,235,236],{},"Console.WriteLine(b.HasValue);  \u002F\u002F True\n",[173,238,240],{"class":175,"line":239},11,[173,241,242],{},"Console.WriteLine(b.Value);     \u002F\u002F 42\n",[173,244,246],{"class":175,"line":245},12,[173,247,201],{"emptyLinePlaceholder":200},[173,249,251],{"class":175,"line":250},13,[173,252,253],{},"Console.WriteLine(c.HasValue);  \u002F\u002F False\n",[173,255,257,260],{"class":175,"line":256},14,[173,258,259],{},"\u002F\u002F Console.WriteLine(c.Value);",[173,261,262],{},"  \u002F\u002F InvalidOperationException!\n",[173,264,266],{"class":175,"line":265},15,[173,267,268],{},"Console.WriteLine(c.GetValueOrDefault()); \u002F\u002F 0 — safe, no throw\n",[173,270,272],{"class":175,"line":271},16,[173,273,274],{},"Console.WriteLine(c.GetValueOrDefault(-1)); \u002F\u002F -1 — custom fallback\n",[15,276,277,278,281,282,285,286,289,290,293,294,297,298,301],{},"At runtime, ",[19,279,280],{},"Nullable\u003Cint>"," is a struct containing ",[19,283,284],{},"bool HasValue"," and ",[19,287,288],{},"int Value",".\nThe compiler synthesises special handling: ",[19,291,292],{},"int? x = null"," compiles to\n",[19,295,296],{},"Nullable\u003Cint> x = new Nullable\u003Cint>()"," (where ",[19,299,300],{},"HasValue = false",").",[303,304,306],"h3",{"id":305},"where-nullable-value-types-are-essential","Where nullable value types are essential",[15,308,309],{},"Database columns that allow NULL are the primary use case:",[164,311,313],{"className":166,"code":312,"language":168,"meta":169,"style":169},"\u002F\u002F Entity Framework model:\npublic class Product\n{\n    public int Id { get; set; }\n    public string Name { get; set; } = \"\";\n    public DateTime? DiscontinuedDate { get; set; } \u002F\u002F null = still active\n    public decimal? Weight { get; set; }             \u002F\u002F null = not measured\n}\n\n\u002F\u002F Querying:\nvar active = db.Products.Where(p => p.DiscontinuedDate == null).ToList();\n",[19,314,315,320,325,330,335,340,345,350,355,359,364],{"__ignoreMap":169},[173,316,317],{"class":175,"line":176},[173,318,319],{},"\u002F\u002F Entity Framework model:\n",[173,321,322],{"class":175,"line":182},[173,323,324],{},"public class Product\n",[173,326,327],{"class":175,"line":188},[173,328,329],{},"{\n",[173,331,332],{"class":175,"line":197},[173,333,334],{},"    public int Id { get; set; }\n",[173,336,337],{"class":175,"line":204},[173,338,339],{},"    public string Name { get; set; } = \"\";\n",[173,341,342],{"class":175,"line":210},[173,343,344],{},"    public DateTime? DiscontinuedDate { get; set; } \u002F\u002F null = still active\n",[173,346,347],{"class":175,"line":216},[173,348,349],{},"    public decimal? Weight { get; set; }             \u002F\u002F null = not measured\n",[173,351,352],{"class":175,"line":222},[173,353,354],{},"}\n",[173,356,357],{"class":175,"line":227},[173,358,201],{"emptyLinePlaceholder":200},[173,360,361],{"class":175,"line":233},[173,362,363],{},"\u002F\u002F Querying:\n",[173,365,366],{"class":175,"line":239},[173,367,368],{},"var active = db.Products.Where(p => p.DiscontinuedDate == null).ToList();\n",[15,370,371,372,375],{},"Nullable value types also appear in: optional form fields, configuration values with\ndefaults, three-state booleans (",[19,373,374],{},"bool?"," for true\u002Ffalse\u002Funknown), and nullable method\nreturn values when \"no result\" is a valid state distinct from any value.",[10,377,379],{"id":378},"the-null-coalescing-operator","The null-coalescing operator ??",[15,381,382,385],{},[19,383,384],{},"??"," returns the left operand if non-null, otherwise the right operand. It short-circuits\n— the right side is not evaluated if the left is non-null.",[164,387,389],{"className":166,"code":388,"language":168,"meta":169,"style":169},"string? name = GetName(); \u002F\u002F may return null\n\n\u002F\u002F Verbose form:\nstring display = name != null ? name : \"Guest\";\n\n\u002F\u002F With ??:\nstring display = name ?? \"Guest\";\n\n\u002F\u002F Chain for fallback hierarchy:\nstring value = GetFromCache()\n            ?? GetFromDatabase()\n            ?? GetFromConfig()\n            ?? \"default\";\n\n\u002F\u002F Nullable value type:\nint? optionalCount = GetCount(); \u002F\u002F may be null\nint  count         = optionalCount ?? 0;\n\n\u002F\u002F Lazy field initialisation:\nprivate string? _label;\npublic string Label => _label ?? (_label = ComputeLabel()); \u002F\u002F initialise on first access\n",[19,390,391,396,400,405,410,414,419,424,428,433,438,443,448,453,457,462,467,473,478,484,490],{"__ignoreMap":169},[173,392,393],{"class":175,"line":176},[173,394,395],{},"string? name = GetName(); \u002F\u002F may return null\n",[173,397,398],{"class":175,"line":182},[173,399,201],{"emptyLinePlaceholder":200},[173,401,402],{"class":175,"line":188},[173,403,404],{},"\u002F\u002F Verbose form:\n",[173,406,407],{"class":175,"line":197},[173,408,409],{},"string display = name != null ? name : \"Guest\";\n",[173,411,412],{"class":175,"line":204},[173,413,201],{"emptyLinePlaceholder":200},[173,415,416],{"class":175,"line":210},[173,417,418],{},"\u002F\u002F With ??:\n",[173,420,421],{"class":175,"line":216},[173,422,423],{},"string display = name ?? \"Guest\";\n",[173,425,426],{"class":175,"line":222},[173,427,201],{"emptyLinePlaceholder":200},[173,429,430],{"class":175,"line":227},[173,431,432],{},"\u002F\u002F Chain for fallback hierarchy:\n",[173,434,435],{"class":175,"line":233},[173,436,437],{},"string value = GetFromCache()\n",[173,439,440],{"class":175,"line":239},[173,441,442],{},"            ?? GetFromDatabase()\n",[173,444,445],{"class":175,"line":245},[173,446,447],{},"            ?? GetFromConfig()\n",[173,449,450],{"class":175,"line":250},[173,451,452],{},"            ?? \"default\";\n",[173,454,455],{"class":175,"line":256},[173,456,201],{"emptyLinePlaceholder":200},[173,458,459],{"class":175,"line":265},[173,460,461],{},"\u002F\u002F Nullable value type:\n",[173,463,464],{"class":175,"line":271},[173,465,466],{},"int? optionalCount = GetCount(); \u002F\u002F may be null\n",[173,468,470],{"class":175,"line":469},17,[173,471,472],{},"int  count         = optionalCount ?? 0;\n",[173,474,476],{"class":175,"line":475},18,[173,477,201],{"emptyLinePlaceholder":200},[173,479,481],{"class":175,"line":480},19,[173,482,483],{},"\u002F\u002F Lazy field initialisation:\n",[173,485,487],{"class":175,"line":486},20,[173,488,489],{},"private string? _label;\n",[173,491,493],{"class":175,"line":492},21,[173,494,495],{},"public string Label => _label ?? (_label = ComputeLabel()); \u002F\u002F initialise on first access\n",[303,497,499],{"id":498},"null-coalescing-assignment","Null-coalescing assignment ??=",[15,501,502,505],{},[19,503,504],{},"??="," assigns the right side to the variable only if the variable is currently null.",[164,507,509],{"className":166,"code":508,"language":168,"meta":169,"style":169},"\u002F\u002F Without ??=:\nif (_cache == null)\n    _cache = new Dictionary\u003Cstring, object>();\n\n\u002F\u002F With ??= (C# 8+):\n_cache ??= new Dictionary\u003Cstring, object>();\n\n\u002F\u002F Property initialisation:\nprivate List\u003Cstring>? _items;\npublic List\u003Cstring> Items => _items ??= new List\u003Cstring>();\n\u002F\u002F First access creates the list; subsequent accesses return it\n",[19,510,511,516,521,526,530,535,540,544,549,554,559],{"__ignoreMap":169},[173,512,513],{"class":175,"line":176},[173,514,515],{},"\u002F\u002F Without ??=:\n",[173,517,518],{"class":175,"line":182},[173,519,520],{},"if (_cache == null)\n",[173,522,523],{"class":175,"line":188},[173,524,525],{},"    _cache = new Dictionary\u003Cstring, object>();\n",[173,527,528],{"class":175,"line":197},[173,529,201],{"emptyLinePlaceholder":200},[173,531,532],{"class":175,"line":204},[173,533,534],{},"\u002F\u002F With ??= (C# 8+):\n",[173,536,537],{"class":175,"line":210},[173,538,539],{},"_cache ??= new Dictionary\u003Cstring, object>();\n",[173,541,542],{"class":175,"line":216},[173,543,201],{"emptyLinePlaceholder":200},[173,545,546],{"class":175,"line":222},[173,547,548],{},"\u002F\u002F Property initialisation:\n",[173,550,551],{"class":175,"line":227},[173,552,553],{},"private List\u003Cstring>? _items;\n",[173,555,556],{"class":175,"line":233},[173,557,558],{},"public List\u003Cstring> Items => _items ??= new List\u003Cstring>();\n",[173,560,561],{"class":175,"line":239},[173,562,563],{},"\u002F\u002F First access creates the list; subsequent accesses return it\n",[10,565,567],{"id":566},"the-null-conditional-operator","The null-conditional operator ?.",[15,569,570,573,574,577,578,580],{},[19,571,572],{},"?."," navigates a chain of member accesses, returning ",[19,575,576],{},"null"," at the first null link\ninstead of throwing ",[19,579,21],{},". It converts the result to a nullable\ntype when the final member is a value type.",[164,582,584],{"className":166,"code":583,"language":168,"meta":169,"style":169},"User? user = GetUser(id); \u002F\u002F may return null\n\n\u002F\u002F Without ?.: three levels of null checking\nstring? city = null;\nif (user != null && user.Address != null)\n    city = user.Address.City;\n\n\u002F\u002F With ?.:\nstring? city = user?.Address?.City; \u002F\u002F null if user or Address is null\n\n\u002F\u002F With value types — result becomes nullable:\nint? orderCount = user?.Orders?.Count; \u002F\u002F int? because Count is int\n\n\u002F\u002F Safe event invocation — the classic use case:\nEventHandler? OnChanged;\nOnChanged?.Invoke(this, EventArgs.Empty); \u002F\u002F skip if no subscribers\n\n\u002F\u002F Index notation:\nstring? firstOrderItem = user?.Orders?[0]?.Description;\n\n\u002F\u002F Combine with ??: provide a default\nstring display = user?.Name ?? \"Guest\";\nint    count   = user?.Orders?.Count ?? 0;\n",[19,585,586,591,595,600,605,610,615,619,624,629,633,638,643,647,652,657,662,666,671,676,680,685,691],{"__ignoreMap":169},[173,587,588],{"class":175,"line":176},[173,589,590],{},"User? user = GetUser(id); \u002F\u002F may return null\n",[173,592,593],{"class":175,"line":182},[173,594,201],{"emptyLinePlaceholder":200},[173,596,597],{"class":175,"line":188},[173,598,599],{},"\u002F\u002F Without ?.: three levels of null checking\n",[173,601,602],{"class":175,"line":197},[173,603,604],{},"string? city = null;\n",[173,606,607],{"class":175,"line":204},[173,608,609],{},"if (user != null && user.Address != null)\n",[173,611,612],{"class":175,"line":210},[173,613,614],{},"    city = user.Address.City;\n",[173,616,617],{"class":175,"line":216},[173,618,201],{"emptyLinePlaceholder":200},[173,620,621],{"class":175,"line":222},[173,622,623],{},"\u002F\u002F With ?.:\n",[173,625,626],{"class":175,"line":227},[173,627,628],{},"string? city = user?.Address?.City; \u002F\u002F null if user or Address is null\n",[173,630,631],{"class":175,"line":233},[173,632,201],{"emptyLinePlaceholder":200},[173,634,635],{"class":175,"line":239},[173,636,637],{},"\u002F\u002F With value types — result becomes nullable:\n",[173,639,640],{"class":175,"line":245},[173,641,642],{},"int? orderCount = user?.Orders?.Count; \u002F\u002F int? because Count is int\n",[173,644,645],{"class":175,"line":250},[173,646,201],{"emptyLinePlaceholder":200},[173,648,649],{"class":175,"line":256},[173,650,651],{},"\u002F\u002F Safe event invocation — the classic use case:\n",[173,653,654],{"class":175,"line":265},[173,655,656],{},"EventHandler? OnChanged;\n",[173,658,659],{"class":175,"line":271},[173,660,661],{},"OnChanged?.Invoke(this, EventArgs.Empty); \u002F\u002F skip if no subscribers\n",[173,663,664],{"class":175,"line":469},[173,665,201],{"emptyLinePlaceholder":200},[173,667,668],{"class":175,"line":475},[173,669,670],{},"\u002F\u002F Index notation:\n",[173,672,673],{"class":175,"line":480},[173,674,675],{},"string? firstOrderItem = user?.Orders?[0]?.Description;\n",[173,677,678],{"class":175,"line":486},[173,679,201],{"emptyLinePlaceholder":200},[173,681,682],{"class":175,"line":492},[173,683,684],{},"\u002F\u002F Combine with ??: provide a default\n",[173,686,688],{"class":175,"line":687},22,[173,689,690],{},"string display = user?.Name ?? \"Guest\";\n",[173,692,694],{"class":175,"line":693},23,[173,695,696],{},"int    count   = user?.Orders?.Count ?? 0;\n",[15,698,699,700,702],{},"The ",[19,701,572],{}," chain short-circuits: once a null is encountered, the entire expression\nevaluates to null without executing further member accesses or method calls.",[10,704,706],{"id":705},"nullable-reference-types-c-8","Nullable reference types — C# 8",[15,708,709,710,712,713,717],{},"Before C# 8, ",[19,711,126],{}," could be null at any time — the type system provided no way to\ndistinguish \"this string is always set\" from \"this string might be absent.\" C# 8\nintroduced a ",[714,715,716],"strong",{},"compile-time annotation layer"," (not a runtime change) that lets you\nexpress this intent.",[164,719,721],{"className":166,"code":720,"language":168,"meta":169,"style":169},"#nullable enable\n\n\u002F\u002F Non-nullable — must always be a valid string:\nstring name = \"Alice\";     \u002F\u002F fine\n\u002F\u002F name = null;             \u002F\u002F CS8600: cannot assign null to non-nullable string\n\n\u002F\u002F Nullable — can be null:\nstring? nickname = null;   \u002F\u002F fine\nConsole.WriteLine(nickname.Length); \u002F\u002F CS8602: dereference of possibly null reference\n\n\u002F\u002F Safe access patterns the compiler accepts:\nif (nickname != null)\n    Console.WriteLine(nickname.Length); \u002F\u002F flow analysis narrows to non-null\n\nConsole.WriteLine(nickname?.Length);    \u002F\u002F null-conditional returns null safely\nConsole.WriteLine(nickname?.Length ?? 0); \u002F\u002F with default\n",[19,722,723,728,732,737,742,750,754,759,764,769,773,778,783,788,792,797],{"__ignoreMap":169},[173,724,725],{"class":175,"line":176},[173,726,727],{},"#nullable enable\n",[173,729,730],{"class":175,"line":182},[173,731,201],{"emptyLinePlaceholder":200},[173,733,734],{"class":175,"line":188},[173,735,736],{},"\u002F\u002F Non-nullable — must always be a valid string:\n",[173,738,739],{"class":175,"line":197},[173,740,741],{},"string name = \"Alice\";     \u002F\u002F fine\n",[173,743,744,747],{"class":175,"line":204},[173,745,746],{},"\u002F\u002F name = null;",[173,748,749],{},"             \u002F\u002F CS8600: cannot assign null to non-nullable string\n",[173,751,752],{"class":175,"line":210},[173,753,201],{"emptyLinePlaceholder":200},[173,755,756],{"class":175,"line":216},[173,757,758],{},"\u002F\u002F Nullable — can be null:\n",[173,760,761],{"class":175,"line":222},[173,762,763],{},"string? nickname = null;   \u002F\u002F fine\n",[173,765,766],{"class":175,"line":227},[173,767,768],{},"Console.WriteLine(nickname.Length); \u002F\u002F CS8602: dereference of possibly null reference\n",[173,770,771],{"class":175,"line":233},[173,772,201],{"emptyLinePlaceholder":200},[173,774,775],{"class":175,"line":239},[173,776,777],{},"\u002F\u002F Safe access patterns the compiler accepts:\n",[173,779,780],{"class":175,"line":245},[173,781,782],{},"if (nickname != null)\n",[173,784,785],{"class":175,"line":250},[173,786,787],{},"    Console.WriteLine(nickname.Length); \u002F\u002F flow analysis narrows to non-null\n",[173,789,790],{"class":175,"line":256},[173,791,201],{"emptyLinePlaceholder":200},[173,793,794],{"class":175,"line":265},[173,795,796],{},"Console.WriteLine(nickname?.Length);    \u002F\u002F null-conditional returns null safely\n",[173,798,799],{"class":175,"line":271},[173,800,801],{},"Console.WriteLine(nickname?.Length ?? 0); \u002F\u002F with default\n",[303,803,805],{"id":804},"enabling-nullable-reference-types","Enabling nullable reference types",[164,807,811],{"className":808,"code":809,"language":810,"meta":169,"style":169},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- Project-wide in .csproj — recommended for all new projects -->\n\u003CPropertyGroup>\n  \u003CNullable>enable\u003C\u002FNullable>\n\u003C\u002FPropertyGroup>\n","xml",[19,812,813,818,823,828],{"__ignoreMap":169},[173,814,815],{"class":175,"line":176},[173,816,817],{},"\u003C!-- Project-wide in .csproj — recommended for all new projects -->\n",[173,819,820],{"class":175,"line":182},[173,821,822],{},"\u003CPropertyGroup>\n",[173,824,825],{"class":175,"line":188},[173,826,827],{},"  \u003CNullable>enable\u003C\u002FNullable>\n",[173,829,830],{"class":175,"line":197},[173,831,832],{},"\u003C\u002FPropertyGroup>\n",[164,834,836],{"className":166,"code":835,"language":168,"meta":169,"style":169},"\u002F\u002F Per-file (incremental migration of existing code):\n#nullable enable    \u002F\u002F enable for remainder of this file\n#nullable disable   \u002F\u002F disable (for legacy code you haven't migrated yet)\n#nullable restore   \u002F\u002F restore to project default\n",[19,837,838,843,851,859],{"__ignoreMap":169},[173,839,840],{"class":175,"line":176},[173,841,842],{},"\u002F\u002F Per-file (incremental migration of existing code):\n",[173,844,845,848],{"class":175,"line":182},[173,846,847],{},"#nullable enable",[173,849,850],{},"    \u002F\u002F enable for remainder of this file\n",[173,852,853,856],{"class":175,"line":188},[173,854,855],{},"#nullable disable",[173,857,858],{},"   \u002F\u002F disable (for legacy code you haven't migrated yet)\n",[173,860,861,864],{"class":175,"line":197},[173,862,863],{},"#nullable restore",[173,865,866],{},"   \u002F\u002F restore to project default\n",[15,868,869,870,873],{},"Annotations only (",[19,871,872],{},"#nullable enable annotations",") enables the annotation syntax\nwithout emitting warnings — useful for marking up APIs before you are ready to fix\nall call sites.",[303,875,877],{"id":876},"flow-analysis","Flow analysis",[15,879,880,881,884],{},"The C# compiler performs ",[714,882,883],{},"null-state flow analysis"," through branches, null checks,\nand pattern matching:",[164,886,888],{"className":166,"code":887,"language":168,"meta":169,"style":169},"#nullable enable\n\nvoid Process(string? value)\n{\n    \u002F\u002F value is \"maybe null\" here\n    Console.WriteLine(value.Length); \u002F\u002F warning\n\n    if (value == null)\n        return;\n\n    \u002F\u002F value is \"not null\" here — flow analysis narrows it:\n    Console.WriteLine(value.Length); \u002F\u002F no warning\n\n    \u002F\u002F Pattern matching also narrows:\n    if (value is { Length: > 0 })\n        Console.WriteLine(value.ToUpper()); \u002F\u002F non-null\n}\n",[19,889,890,894,898,903,907,912,917,921,926,931,935,940,945,949,954,959,964],{"__ignoreMap":169},[173,891,892],{"class":175,"line":176},[173,893,727],{},[173,895,896],{"class":175,"line":182},[173,897,201],{"emptyLinePlaceholder":200},[173,899,900],{"class":175,"line":188},[173,901,902],{},"void Process(string? value)\n",[173,904,905],{"class":175,"line":197},[173,906,329],{},[173,908,909],{"class":175,"line":204},[173,910,911],{},"    \u002F\u002F value is \"maybe null\" here\n",[173,913,914],{"class":175,"line":210},[173,915,916],{},"    Console.WriteLine(value.Length); \u002F\u002F warning\n",[173,918,919],{"class":175,"line":216},[173,920,201],{"emptyLinePlaceholder":200},[173,922,923],{"class":175,"line":222},[173,924,925],{},"    if (value == null)\n",[173,927,928],{"class":175,"line":227},[173,929,930],{},"        return;\n",[173,932,933],{"class":175,"line":233},[173,934,201],{"emptyLinePlaceholder":200},[173,936,937],{"class":175,"line":239},[173,938,939],{},"    \u002F\u002F value is \"not null\" here — flow analysis narrows it:\n",[173,941,942],{"class":175,"line":245},[173,943,944],{},"    Console.WriteLine(value.Length); \u002F\u002F no warning\n",[173,946,947],{"class":175,"line":250},[173,948,201],{"emptyLinePlaceholder":200},[173,950,951],{"class":175,"line":256},[173,952,953],{},"    \u002F\u002F Pattern matching also narrows:\n",[173,955,956],{"class":175,"line":265},[173,957,958],{},"    if (value is { Length: > 0 })\n",[173,960,961],{"class":175,"line":271},[173,962,963],{},"        Console.WriteLine(value.ToUpper()); \u002F\u002F non-null\n",[173,965,966],{"class":175,"line":469},[173,967,354],{},[303,969,971],{"id":970},"the-null-forgiving-operator","The null-forgiving operator !",[15,973,974,977,978,981,982,984],{},[19,975,976],{},"!"," suppresses a nullable warning by asserting to the compiler \"I know this is not\nnull.\" It has ",[714,979,980],{},"zero runtime effect"," — if you are wrong, you get a\n",[19,983,21],{}," with no warning.",[164,986,988],{"className":166,"code":987,"language":168,"meta":169,"style":169},"#nullable enable\n\n\u002F\u002F Legitimate use: field set in a method the analyser cannot track\nprivate string _name = null!; \u002F\u002F set in Initialize(), before any use\n\n\u002F\u002F Test setup:\nprivate MyService _service = null!; \u002F\u002F assigned in [SetUp] method\n\n\u002F\u002F After a manually verified check:\nDebug.Assert(result != null);\nConsole.WriteLine(result!.Name); \u002F\u002F analyser can't track Assert; ! suppresses warning\n\n\u002F\u002F Bad use — just hiding the problem:\nstring? couldBeNull = GetData();\nConsole.WriteLine(couldBeNull!.Length); \u002F\u002F might blow up at runtime\n",[19,989,990,994,998,1003,1008,1012,1017,1022,1026,1031,1036,1041,1045,1050,1055],{"__ignoreMap":169},[173,991,992],{"class":175,"line":176},[173,993,727],{},[173,995,996],{"class":175,"line":182},[173,997,201],{"emptyLinePlaceholder":200},[173,999,1000],{"class":175,"line":188},[173,1001,1002],{},"\u002F\u002F Legitimate use: field set in a method the analyser cannot track\n",[173,1004,1005],{"class":175,"line":197},[173,1006,1007],{},"private string _name = null!; \u002F\u002F set in Initialize(), before any use\n",[173,1009,1010],{"class":175,"line":204},[173,1011,201],{"emptyLinePlaceholder":200},[173,1013,1014],{"class":175,"line":210},[173,1015,1016],{},"\u002F\u002F Test setup:\n",[173,1018,1019],{"class":175,"line":216},[173,1020,1021],{},"private MyService _service = null!; \u002F\u002F assigned in [SetUp] method\n",[173,1023,1024],{"class":175,"line":222},[173,1025,201],{"emptyLinePlaceholder":200},[173,1027,1028],{"class":175,"line":227},[173,1029,1030],{},"\u002F\u002F After a manually verified check:\n",[173,1032,1033],{"class":175,"line":233},[173,1034,1035],{},"Debug.Assert(result != null);\n",[173,1037,1038],{"class":175,"line":239},[173,1039,1040],{},"Console.WriteLine(result!.Name); \u002F\u002F analyser can't track Assert; ! suppresses warning\n",[173,1042,1043],{"class":175,"line":245},[173,1044,201],{"emptyLinePlaceholder":200},[173,1046,1047],{"class":175,"line":250},[173,1048,1049],{},"\u002F\u002F Bad use — just hiding the problem:\n",[173,1051,1052],{"class":175,"line":256},[173,1053,1054],{},"string? couldBeNull = GetData();\n",[173,1056,1057],{"class":175,"line":265},[173,1058,1059],{},"Console.WriteLine(couldBeNull!.Length); \u002F\u002F might blow up at runtime\n",[15,1061,1062,1063,1065,1066,1068],{},"Use ",[19,1064,976],{}," sparingly — treat each occurrence as a TODO comment that deserves justification.\nIf you find yourself writing ",[19,1067,976],{}," frequently, refactor the code so the analyser can\ninfer non-nullability.",[10,1070,1072],{"id":1071},"pattern-matching-and-null","Pattern matching and null",[15,1074,1075],{},"Pattern matching in C# provides clean, composable null handling that integrates\nwith the compiler's flow analysis:",[164,1077,1079],{"className":166,"code":1078,"language":168,"meta":169,"style":169},"#nullable enable\n\nobject? Classify(object? value) => value switch\n{\n    null               => \"nothing\",                    \u002F\u002F null arm\n    int n when n \u003C 0   => $\"negative: {n}\",\n    int n              => $\"positive: {n}\",\n    string { Length: 0 } => \"empty string\",            \u002F\u002F property pattern\n    string s           => $\"string: {s}\",\n    _                  => \"something else\",\n};\n\n\u002F\u002F 'is' pattern with type check and null check in one:\nif (value is string s)  \u002F\u002F only matches if value is non-null AND is string\n    Console.WriteLine(s.ToUpper());\n\n\u002F\u002F 'is not null' — preferred over '!= null' since C# 9:\nif (value is not null)\n    Console.WriteLine(value.GetType());\n\n\u002F\u002F Property pattern with null check:\nif (user is { Address.City: \"London\" })  \u002F\u002F safely checks City even if Address could be null\n    Console.WriteLine(\"London user\");\n",[19,1080,1081,1085,1089,1094,1098,1103,1108,1113,1118,1123,1128,1133,1137,1142,1147,1152,1156,1161,1166,1171,1175,1180,1185],{"__ignoreMap":169},[173,1082,1083],{"class":175,"line":176},[173,1084,727],{},[173,1086,1087],{"class":175,"line":182},[173,1088,201],{"emptyLinePlaceholder":200},[173,1090,1091],{"class":175,"line":188},[173,1092,1093],{},"object? Classify(object? value) => value switch\n",[173,1095,1096],{"class":175,"line":197},[173,1097,329],{},[173,1099,1100],{"class":175,"line":204},[173,1101,1102],{},"    null               => \"nothing\",                    \u002F\u002F null arm\n",[173,1104,1105],{"class":175,"line":210},[173,1106,1107],{},"    int n when n \u003C 0   => $\"negative: {n}\",\n",[173,1109,1110],{"class":175,"line":216},[173,1111,1112],{},"    int n              => $\"positive: {n}\",\n",[173,1114,1115],{"class":175,"line":222},[173,1116,1117],{},"    string { Length: 0 } => \"empty string\",            \u002F\u002F property pattern\n",[173,1119,1120],{"class":175,"line":227},[173,1121,1122],{},"    string s           => $\"string: {s}\",\n",[173,1124,1125],{"class":175,"line":233},[173,1126,1127],{},"    _                  => \"something else\",\n",[173,1129,1130],{"class":175,"line":239},[173,1131,1132],{},"};\n",[173,1134,1135],{"class":175,"line":245},[173,1136,201],{"emptyLinePlaceholder":200},[173,1138,1139],{"class":175,"line":250},[173,1140,1141],{},"\u002F\u002F 'is' pattern with type check and null check in one:\n",[173,1143,1144],{"class":175,"line":256},[173,1145,1146],{},"if (value is string s)  \u002F\u002F only matches if value is non-null AND is string\n",[173,1148,1149],{"class":175,"line":265},[173,1150,1151],{},"    Console.WriteLine(s.ToUpper());\n",[173,1153,1154],{"class":175,"line":271},[173,1155,201],{"emptyLinePlaceholder":200},[173,1157,1158],{"class":175,"line":469},[173,1159,1160],{},"\u002F\u002F 'is not null' — preferred over '!= null' since C# 9:\n",[173,1162,1163],{"class":175,"line":475},[173,1164,1165],{},"if (value is not null)\n",[173,1167,1168],{"class":175,"line":480},[173,1169,1170],{},"    Console.WriteLine(value.GetType());\n",[173,1172,1173],{"class":175,"line":486},[173,1174,201],{"emptyLinePlaceholder":200},[173,1176,1177],{"class":175,"line":492},[173,1178,1179],{},"\u002F\u002F Property pattern with null check:\n",[173,1181,1182],{"class":175,"line":687},[173,1183,1184],{},"if (user is { Address.City: \"London\" })  \u002F\u002F safely checks City even if Address could be null\n",[173,1186,1187],{"class":175,"line":693},[173,1188,1189],{},"    Console.WriteLine(\"London user\");\n",[10,1191,1193],{"id":1192},"boxing-and-null-the-special-case","Boxing and null — the special case",[15,1195,1196,1197,1199,1200,1203,1204,1206,1207,1210,1211,1213],{},"When a null ",[19,1198,80],{}," is boxed, the result is a ",[714,1201,1202],{},"true null reference"," (not a\nboxed wrapper around null). When a non-null ",[19,1205,80],{}," is boxed, the underlying\nvalue ",[19,1208,1209],{},"T"," is boxed — not the ",[19,1212,80],{}," wrapper.",[164,1215,1217],{"className":166,"code":1216,"language":168,"meta":169,"style":169},"int?   a = 42;\nobject boxedA = a;         \u002F\u002F boxes to boxed int (System.Int32) — NOT Nullable\u003Cint>\nConsole.WriteLine(boxedA.GetType().Name); \u002F\u002F \"Int32\"\n\nint?   b = null;\nobject boxedB = b;         \u002F\u002F null reference — no heap allocation, no wrapper\nConsole.WriteLine(boxedB == null); \u002F\u002F True\n\n\u002F\u002F Round-trip back:\nint?  recovered = (int?)boxedA;  \u002F\u002F 42\n\u002F\u002F int? wrong = (double?)boxedA; \u002F\u002F InvalidCastException — was boxed as Int32\n",[19,1218,1219,1224,1229,1234,1238,1243,1248,1253,1257,1262,1267],{"__ignoreMap":169},[173,1220,1221],{"class":175,"line":176},[173,1222,1223],{},"int?   a = 42;\n",[173,1225,1226],{"class":175,"line":182},[173,1227,1228],{},"object boxedA = a;         \u002F\u002F boxes to boxed int (System.Int32) — NOT Nullable\u003Cint>\n",[173,1230,1231],{"class":175,"line":188},[173,1232,1233],{},"Console.WriteLine(boxedA.GetType().Name); \u002F\u002F \"Int32\"\n",[173,1235,1236],{"class":175,"line":197},[173,1237,201],{"emptyLinePlaceholder":200},[173,1239,1240],{"class":175,"line":204},[173,1241,1242],{},"int?   b = null;\n",[173,1244,1245],{"class":175,"line":210},[173,1246,1247],{},"object boxedB = b;         \u002F\u002F null reference — no heap allocation, no wrapper\n",[173,1249,1250],{"class":175,"line":216},[173,1251,1252],{},"Console.WriteLine(boxedB == null); \u002F\u002F True\n",[173,1254,1255],{"class":175,"line":222},[173,1256,201],{"emptyLinePlaceholder":200},[173,1258,1259],{"class":175,"line":227},[173,1260,1261],{},"\u002F\u002F Round-trip back:\n",[173,1263,1264],{"class":175,"line":233},[173,1265,1266],{},"int?  recovered = (int?)boxedA;  \u002F\u002F 42\n",[173,1268,1269,1272],{"class":175,"line":239},[173,1270,1271],{},"\u002F\u002F int? wrong = (double?)boxedA;",[173,1273,1274],{}," \u002F\u002F InvalidCastException — was boxed as Int32\n",[15,1276,1277,1278,1280,1281,1284,1285,1287,1288,1290,1291,1293],{},"This design ensures ",[19,1279,576],{}," round-trips cleanly through ",[19,1282,1283],{},"object",". A null ",[19,1286,47],{}," assigned\nto ",[19,1289,1283],{}," gives you ",[19,1292,576],{}," back — not some \"boxed null\" pseudo-object.",[10,1295,1297],{"id":1296},"practical-null-safety-patterns","Practical null-safety patterns",[164,1299,1301],{"className":166,"code":1300,"language":168,"meta":169,"style":169},"#nullable enable\n\n\u002F\u002F 1. Guard clause:\nvoid Process(string? input)\n{\n    ArgumentNullException.ThrowIfNull(input);  \u002F\u002F .NET 6+ one-liner\n    \u002F\u002F input is non-null from here\n}\n\n\u002F\u002F 2. Null object pattern — return a non-null default instead of null:\nUser GetUser(int id) => db.Users.Find(id) ?? User.Anonymous;\n\n\u002F\u002F 3. Optional pattern with records:\nrecord struct Option\u003CT>(T? Value, bool HasValue)\n{\n    public static Option\u003CT> Some(T value) => new(value, true);\n    public static Option\u003CT> None => new(default, false);\n}\n\n\u002F\u002F 4. Factory method instead of nullable constructor result:\n\u002F\u002F Instead of: new Parser(input)?.Parse()\n\u002F\u002F Use: Parser.TryCreate(input, out var parser) ? parser.Parse() : defaultResult\n",[19,1302,1303,1307,1311,1316,1321,1325,1330,1335,1339,1343,1348,1353,1357,1362,1367,1371,1376,1381,1385,1389,1394,1399],{"__ignoreMap":169},[173,1304,1305],{"class":175,"line":176},[173,1306,727],{},[173,1308,1309],{"class":175,"line":182},[173,1310,201],{"emptyLinePlaceholder":200},[173,1312,1313],{"class":175,"line":188},[173,1314,1315],{},"\u002F\u002F 1. Guard clause:\n",[173,1317,1318],{"class":175,"line":197},[173,1319,1320],{},"void Process(string? input)\n",[173,1322,1323],{"class":175,"line":204},[173,1324,329],{},[173,1326,1327],{"class":175,"line":210},[173,1328,1329],{},"    ArgumentNullException.ThrowIfNull(input);  \u002F\u002F .NET 6+ one-liner\n",[173,1331,1332],{"class":175,"line":216},[173,1333,1334],{},"    \u002F\u002F input is non-null from here\n",[173,1336,1337],{"class":175,"line":222},[173,1338,354],{},[173,1340,1341],{"class":175,"line":227},[173,1342,201],{"emptyLinePlaceholder":200},[173,1344,1345],{"class":175,"line":233},[173,1346,1347],{},"\u002F\u002F 2. Null object pattern — return a non-null default instead of null:\n",[173,1349,1350],{"class":175,"line":239},[173,1351,1352],{},"User GetUser(int id) => db.Users.Find(id) ?? User.Anonymous;\n",[173,1354,1355],{"class":175,"line":245},[173,1356,201],{"emptyLinePlaceholder":200},[173,1358,1359],{"class":175,"line":250},[173,1360,1361],{},"\u002F\u002F 3. Optional pattern with records:\n",[173,1363,1364],{"class":175,"line":256},[173,1365,1366],{},"record struct Option\u003CT>(T? Value, bool HasValue)\n",[173,1368,1369],{"class":175,"line":265},[173,1370,329],{},[173,1372,1373],{"class":175,"line":271},[173,1374,1375],{},"    public static Option\u003CT> Some(T value) => new(value, true);\n",[173,1377,1378],{"class":175,"line":469},[173,1379,1380],{},"    public static Option\u003CT> None => new(default, false);\n",[173,1382,1383],{"class":175,"line":475},[173,1384,354],{},[173,1386,1387],{"class":175,"line":480},[173,1388,201],{"emptyLinePlaceholder":200},[173,1390,1391],{"class":175,"line":486},[173,1392,1393],{},"\u002F\u002F 4. Factory method instead of nullable constructor result:\n",[173,1395,1396],{"class":175,"line":492},[173,1397,1398],{},"\u002F\u002F Instead of: new Parser(input)?.Parse()\n",[173,1400,1401],{"class":175,"line":687},[173,1402,1403],{},"\u002F\u002F Use: Parser.TryCreate(input, out var parser) ? parser.Parse() : defaultResult\n",[10,1405,1407],{"id":1406},"recap","Recap",[15,1409,1410,1411,1414,1415,1417,1418,1420,1421,285,1423,1426,1427,1430,1431,1433,1434,1436,1437,1439,1440,1442,1443,1445,1446,1448,1449,1451,1452,138,1455,1458,1459,1461],{},"C# has two distinct null systems. ",[714,1412,1413],{},"Nullable value types"," (",[19,1416,47],{}," = ",[19,1419,280],{},")\nare a runtime CLR feature — a struct with ",[19,1422,161],{},[19,1424,1425],{},"Value"," fields that can\nrepresent null for any non-nullable value type. ",[714,1428,1429],{},"Nullable reference types"," (C# 8's\n",[19,1432,54],{}," vs ",[19,1435,126],{},") are a compile-time annotation feature with no runtime overhead —\nthey let the compiler perform null-state flow analysis and warn on potential\n",[19,1438,21],{}," before it happens. The ",[19,1441,384],{}," operator provides null coalescing,\n",[19,1444,572],{}," provides safe navigation through object chains, and ",[19,1447,504],{}," is lazy assignment that\nonly fires on null. The null-forgiving operator ",[19,1450,976],{}," suppresses analyser warnings when\nyou have a guarantee the tool cannot infer — use it sparingly. Pattern matching with\n",[19,1453,1454],{},"is not null",[19,1456,1457],{},"is { }",", and switch expressions integrates null handling cleanly with\nthe compiler's flow analysis. Enabling ",[19,1460,114],{}," in new projects\nfrom day one catches most null bugs at compile time rather than at runtime.",[1463,1464,1465],"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":169,"searchDepth":182,"depth":182,"links":1467},[1468,1469,1470,1473,1476,1477,1482,1483,1484,1485],{"id":12,"depth":182,"text":13},{"id":25,"depth":182,"text":26},{"id":130,"depth":182,"text":131,"children":1471},[1472],{"id":305,"depth":188,"text":306},{"id":378,"depth":182,"text":379,"children":1474},[1475],{"id":498,"depth":188,"text":499},{"id":566,"depth":182,"text":567},{"id":705,"depth":182,"text":706,"children":1478},[1479,1480,1481],{"id":804,"depth":188,"text":805},{"id":876,"depth":188,"text":877},{"id":970,"depth":188,"text":971},{"id":1071,"depth":182,"text":1072},{"id":1192,"depth":182,"text":1193},{"id":1296,"depth":182,"text":1297},{"id":1406,"depth":182,"text":1407},"How null works in C# — the difference between nullable value types and C# 8 nullable reference types, the operators that make null handling safe, and how flow analysis catches NullReferenceExceptions at compile time.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-nullable-types-null-safety-csharp8","\u002Fdotnet\u002Ffundamentals\u002Fnullable-types",{"title":5,"description":1486},"blog\u002Fdotnet-nullable-types-null-safety-csharp8","Nullable Types","Fundamentals","fundamentals","2026-06-22","64Zp6iIjBVvPJ6Fg6ZnjctLChCuTjc7If-BFD53Pkvg",1782244087833]