{"id":2280,"date":"2026-02-20T20:27:34","date_gmt":"2026-02-20T20:27:34","guid":{"rendered":"https:\/\/www.w3computing.com\/articles\/?p=2280"},"modified":"2026-02-20T20:27:42","modified_gmt":"2026-02-20T20:27:42","slug":"semi-auto-properties-field-keyword-csharp","status":"publish","type":"post","link":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/","title":{"rendered":"Semi-Auto Properties and the field Keyword in C#"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">1. Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Auto-properties were a welcome addition back in C# 3.0. Instead of declaring a private backing field, writing a getter that returns it, and writing a setter that assigns to it, you could collapse all of that into a single line. Clean, fast, done.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But that convenience came with a hard ceiling. The moment you needed <em>anything<\/em> beyond a straight get\/set \u2014 a range check in the setter, a default value computed at first access, a bit of transformation before storing a value \u2014 the compiler-generated backing field became completely inaccessible to you. Your only option was to abandon the auto-property entirely: declare an explicit <code>_value<\/code> field, re-wire the getter and setter by hand, and end up with four to eight lines of boilerplate where one used to live. For something as common as input validation, that felt like a disproportionate tax.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C# 13 addresses this with the <code>field<\/code> keyword and the concept of <em>semi-auto properties<\/em>. The idea is straightforward: let the compiler still generate the backing field, but give you access to it inside the property body through the contextual keyword <code>field<\/code>. You keep the convenience of not naming the field yourself, and you get the flexibility to add logic around it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s a small addition to the language. The impact on day-to-day code is anything but.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. A Quick Refresher: Auto-Properties vs. Full Properties<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before diving into <code>field<\/code>, it&#8217;s worth grounding ourselves in the two patterns it sits between, because semi-auto properties only make sense as a middle ground once you feel the friction at both ends.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Auto-properties<\/strong> are the ones you write without thinking:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Name { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">set<\/span>; }\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Price { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">set<\/span>; }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The compiler generates a private backing field for each one behind the scenes. You never see it, you never touch it, and that&#8217;s the whole point. For simple data-carrier types, this is exactly what you want.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The trouble starts when requirements creep in. Say <code>Price<\/code> can&#8217;t be negative. Now you need to intercept the setter. And the moment you do that, auto-property syntax is no longer on the table:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">decimal<\/span> _price;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Name { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">set<\/span>; }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Price\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _price;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt; <span class=\"hljs-number\">0<\/span>)\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>), <span class=\"hljs-string\">\"Price cannot be negative.\"<\/span>);\n            _price = <span class=\"hljs-keyword\">value<\/span>;\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s a significant jump in verbosity for what is, conceptually, a very minor change in behavior. You&#8217;ve gone from one line to nine, and you&#8217;ve had to introduce a private field with a name you now have to maintain, keep consistent with any naming conventions your team follows, and make sure never collides with anything else. The logic itself \u2014 the actual interesting part \u2014 is just two lines buried in the middle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the gap that exists between auto-properties and full properties, and it&#8217;s a gap C# developers cross dozens of times in any reasonably sized codebase. Renaming a field during a refactor, keeping <code>_price<\/code> and <code>Price<\/code> in sync, making sure you didn&#8217;t accidentally reference the field directly somewhere and bypass the validation \u2014 none of it is hard, but all of it is noise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties live exactly in this space. Same compiled output, far less ceremony.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. What Are Semi-Auto Properties?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The name &#8220;semi-auto property&#8221; isn&#8217;t an official C# specification term \u2014 it&#8217;s the shorthand the community and the C# team landed on to describe what this feature actually is: a property where the compiler still generates the backing field, but you write at least one accessor body yourself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The mechanism is simple. Inside any property accessor, you can now use the contextual keyword <code>field<\/code> to refer to the compiler-synthesized backing field for that property. That&#8217;s it. No declaration, no naming, no <code>private decimal _price<\/code> sitting above the property. The field exists because the compiler creates it, and <code>field<\/code> is how you reach it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s the <code>Price<\/code> example from the previous section, rewritten as a semi-auto property:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Name { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">set<\/span>; }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Price\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt; <span class=\"hljs-number\">0<\/span>)\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>), <span class=\"hljs-string\">\"Price cannot be negative.\"<\/span>);\n            field = <span class=\"hljs-keyword\">value<\/span>;\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Nine lines down to seven, but more importantly, the backing field is gone. There&#8217;s nothing to name, nothing to keep in sync, and no risk of accidentally bypassing the property logic by referencing the field directly elsewhere in the class. The <code>field<\/code> keyword is only accessible inside the property&#8217;s own accessors \u2014 it&#8217;s completely invisible to the rest of the type.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It helps to think about the three property patterns as a clear spectrum:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Auto-property<\/strong>: compiler writes both accessors and the backing field. You control nothing.<\/li>\n\n\n\n<li><strong>Semi-auto property<\/strong>: compiler writes the backing field. You write one or both accessors, using <code>field<\/code> to interact with it.<\/li>\n\n\n\n<li><strong>Full property<\/strong>: you write everything \u2014 the backing field, both accessors, all of it.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties don&#8217;t replace either end of that spectrum. A plain <code>{ get; set; }<\/code> with no extra logic should stay an auto-property. A property that needs two private fields, or that computes its value entirely from other properties with no storage at all, still warrants a full implementation. Semi-auto properties are specifically the answer to the case in the middle: you need a stored value, <em>and<\/em> you need a little logic around it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing worth noting early: <code>field<\/code> is a <em>contextual<\/em> keyword, not a reserved one. That distinction matters, and we&#8217;ll get into the specifics in the next section.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. The <code>field<\/code> Contextual Keyword \u2014 Syntax and Semantics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s get precise about how <code>field<\/code> actually works, because there are a handful of rules and behaviors here that will save you from confusion later \u2014 especially around nullability, attribute targeting, and the contextual vs. reserved keyword distinction.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Contextual, Not Reserved<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>field<\/code> is a contextual keyword, which means it only carries special meaning inside a property accessor body. Everywhere else in your code, <code>field<\/code> is a perfectly valid identifier. You can have a local variable named <code>field<\/code>, a parameter named <code>field<\/code>, a method named <code>field<\/code> \u2014 the compiler figures out from context which one you mean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This was a deliberate language design decision. Making <code>field<\/code> a fully reserved keyword would have been a breaking change for any codebase that already used <code>field<\/code> as an identifier, which is not an uncommon thing in serialization code, reflection utilities, and ORM-related infrastructure. By keeping it contextual, the C# team avoided breaking existing code entirely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That said, this creates one genuine footgun, which we&#8217;ll cover in detail in the gotchas section. For now, just keep in mind: if you have a parameter or local variable named <code>field<\/code> inside an accessor, it will shadow the keyword. The compiler will warn you, but it won&#8217;t stop you.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where <code>field<\/code> Is Valid<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>field<\/code> keyword is valid inside the body of any of the following accessors:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>get<\/code><\/li>\n\n\n\n<li><code>set<\/code><\/li>\n\n\n\n<li><code>init<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It is not valid anywhere else in the class body. You can&#8217;t reference it from a constructor, a method, another property, or an expression outside of these accessor bodies. This is by design \u2014 the whole point is that the backing field is encapsulated within the property itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You also don&#8217;t need to use <code>field<\/code> in both accessors. These are all valid semi-auto properties:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-comment\">\/\/ Only the setter uses field; getter has custom logic<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Slug\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field.ToLowerInvariant().Replace(<span class=\"hljs-string\">' '<\/span>, <span class=\"hljs-string\">'-'<\/span>);\n    <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>.Trim();\n}\n\n<span class=\"hljs-comment\">\/\/ Only the getter uses field; setter is auto-generated<\/span>\n<span class=\"hljs-keyword\">public<\/span> DateTime CreatedAt\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n    <span class=\"hljs-keyword\">set<\/span>;  <span class=\"hljs-comment\">\/\/ compiler handles this, field is assigned directly<\/span>\n}\n\n<span class=\"hljs-comment\">\/\/ Only the setter has logic; getter is auto-generated<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Username\n{\n    <span class=\"hljs-keyword\">get<\/span>;\n    <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>?.Trim() ?? <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentNullException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That last pattern is particularly useful \u2014 you can leave the getter as a simple auto-accessor and only write the setter when that&#8217;s the only side that needs logic, without having to write <code>get =&gt; field;<\/code> explicitly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Type Inference<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The type of <code>field<\/code> is inferred from the property&#8217;s declared type. There&#8217;s no ambiguity here and nothing to configure. If the property is declared as <code>decimal Price<\/code>, then <code>field<\/code> is a <code>decimal<\/code>. If the property is <code>List&lt;string&gt; Tags<\/code>, then <code>field<\/code> is a <code>List&lt;string&gt;<\/code>. You&#8217;re always working with the same type as the property itself.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Nullability<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nullability follows the property&#8217;s declared type as well, and this is where things get slightly nuanced. Consider a nullable reference type scenario:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span>? DisplayName\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field ?? <span class=\"hljs-string\">\"Anonymous\"<\/span>;\n    <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Here <code>field<\/code> is <code>string?<\/code>, matching the property type. The getter returns a non-nullable <code>string<\/code> by coalescing with a default, but the stored value can genuinely be null. The compiler understands this and won&#8217;t raise a nullability warning on the getter&#8217;s return path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Where it gets trickier is when the property type is non-nullable but you want lazy initialization \u2014 initializing <code>field<\/code> on first access rather than at construction time:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Description\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field ??= LoadDescription();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The compiler will warn here because <code>field<\/code> is <code>string<\/code> (non-nullable) yet it&#8217;s being treated as potentially null through the <code>??=<\/code> operator. You&#8217;d need to declare the property as <code>string?<\/code> and handle the nullability at the call site, or suppress the warning explicitly if you&#8217;re confident about the initialization path. This is one of the few places where the feature&#8217;s convenience has a minor rough edge.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Initializers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can provide a property initializer the same way you would with an auto-property, and <code>field<\/code> will be pre-populated with that value:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> RetryCount\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n    <span class=\"hljs-keyword\">set<\/span> =&gt; field = Math.Clamp(<span class=\"hljs-keyword\">value<\/span>, <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">10<\/span>);\n} = <span class=\"hljs-number\">3<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The <code>= 3<\/code> at the end initializes the backing field to <code>3<\/code> before any constructor logic runs, exactly as it would with a plain auto-property. The setter&#8217;s clamping logic does not run during initialization \u2014 the value is written directly to <code>field<\/code> \u2014 so if you need invariants enforced even during initialization, you&#8217;ll want to handle that in the constructor instead.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>LangVersion<\/code> Requirement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties and the <code>field<\/code> keyword require <code>LangVersion<\/code> set to <code>preview<\/code> in C# 13 during the preview period, and will require <code>13<\/code> or later once the feature ships in a stable release. If you&#8217;re targeting an older language version, <code>field<\/code> is simply treated as a regular identifier, and the compiler won&#8217;t synthesize a backing field for it. This can lead to confusing errors if you accidentally use <code>field<\/code> in a codebase with a lower <code>LangVersion<\/code> \u2014 worth checking your project file if something seems off.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Step-by-Step: Migrating Common Patterns to Semi-Auto Properties<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is where the feature earns its keep. Let&#8217;s walk through five patterns you&#8217;ve almost certainly written before \u2014 each one a case where an auto-property wasn&#8217;t enough and a full backing field felt like overkill. For each pattern, we&#8217;ll look at the old approach and then the semi-auto equivalent, with notes on what changed and why.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 1: Validation in Setters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is the canonical use case, and the one the C# team almost certainly had front of mind when designing the feature.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Order<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">int<\/span> _quantity;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> Quantity\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _quantity;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt;= <span class=\"hljs-number\">0<\/span>)\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>), <span class=\"hljs-string\">\"Quantity must be greater than zero.\"<\/span>);\n            _quantity = <span class=\"hljs-keyword\">value<\/span>;\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Order<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> Quantity\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt;= <span class=\"hljs-number\">0<\/span>)\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>), <span class=\"hljs-string\">\"Quantity must be greater than zero.\"<\/span>);\n            field = <span class=\"hljs-keyword\">value<\/span>;\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The private field is gone. The getter is now an auto-accessor \u2014 you don&#8217;t even need to write <code>get =&gt; field;<\/code> explicitly. The setter retains all its validation logic and writes to <code>field<\/code> instead of <code>_quantity<\/code>. The compiled output is identical to the before version; the difference is entirely in how much you had to write and maintain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you have multiple validated properties on the same class, the cumulative reduction in boilerplate is substantial. A class with five validated properties previously meant five private fields declared at the top, five property declarations below them, and the constant discipline of making sure nothing in the class bypassed a property by writing to the field directly. With semi-auto properties, that entire category of concern disappears.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 2: Lazy Initialization in Getters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lazy initialization \u2014 computing or loading a value only when it&#8217;s first requested and caching it for subsequent accesses \u2014 is another pattern that&#8217;s simple in concept but verbose to implement with a full backing field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ReportGenerator<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;? _sections;\n\n    <span class=\"hljs-keyword\">public<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; Sections\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _sections ??= LoadSections();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; <span class=\"hljs-title\">LoadSections<\/span>(<span class=\"hljs-params\"><\/span>)<\/span> { <span class=\"hljs-comment\">\/* ... *\/<\/span> }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ReportGenerator<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;? Sections\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field ??= LoadSections();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; <span class=\"hljs-title\">LoadSections<\/span>(<span class=\"hljs-params\"><\/span>)<\/span> { <span class=\"hljs-comment\">\/* ... *\/<\/span> }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Notice that the property type has to be <code>List&lt;string&gt;?<\/code> here rather than <code>List&lt;string&gt;<\/code>, because <code>field<\/code> starts as null and the nullability analyzer needs to know that&#8217;s intentional. This is the nullability edge case mentioned in the previous section. Whether that nullable annotation leaks into your public API depends on how comfortable you are with suppressing the warning or adjusting call sites \u2014 it&#8217;s a minor trade-off worth being aware of.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That said, for internal properties, private properties, or cases where nullable reference types aren&#8217;t enabled, this pattern is a clean win with zero downsides.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 3: Trimming and Normalizing Input<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">String properties frequently need light sanitization on the way in \u2014 trimming whitespace, normalizing casing, collapsing empty strings to null. This is almost never worth a full backing field, but it&#8217;s always been just out of reach for auto-properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserProfile<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">string<\/span>? _displayName;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span>? DisplayName\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _displayName;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; _displayName = <span class=\"hljs-keyword\">value<\/span>?.Trim();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserProfile<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span>? DisplayName\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>?.Trim();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Short, obvious, and self-contained. The getter needs no custom logic so it stays as an auto-accessor. The setter does its one job. Anyone reading this class knows exactly what <code>DisplayName<\/code> does with assigned values without needing to scroll up to find a backing field declaration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This pattern scales nicely. You might have a <code>UserProfile<\/code> with six string properties that all need trimming. Previously that meant six backing fields. Now it&#8217;s six properties with a one-line setter each and nothing else.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 4: Computed Caching<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sometimes a property&#8217;s value is expensive to compute \u2014 it might involve LINQ aggregation, string building, or any other non-trivial operation \u2014 but the inputs don&#8217;t change often enough to justify recomputing on every access. The classic solution is a nullable backing field used as a cache, cleared whenever the underlying data changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Invoice<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">decimal<\/span>? _total;\n    <span class=\"hljs-keyword\">private<\/span> List&lt;LineItem&gt; _lineItems = <span class=\"hljs-keyword\">new<\/span>();\n\n    <span class=\"hljs-keyword\">public<\/span> List&lt;LineItem&gt; LineItems\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _lineItems;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            _lineItems = <span class=\"hljs-keyword\">value<\/span>;\n            _total = <span class=\"hljs-literal\">null<\/span>; <span class=\"hljs-comment\">\/\/ invalidate cache<\/span>\n        }\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Total\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _total ??= _lineItems.Sum(x =&gt; x.Amount);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Invoice<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> List&lt;LineItem&gt; LineItems\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            field = <span class=\"hljs-keyword\">value<\/span>;\n            Total = <span class=\"hljs-literal\">null<\/span>; <span class=\"hljs-comment\">\/\/ invalidate cache<\/span>\n        }\n    } = <span class=\"hljs-keyword\">new<\/span>();\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span>? Total\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field ??= LineItems.Sum(x =&gt; x.Amount);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Both <code>LineItems<\/code> and <code>Total<\/code> are now semi-auto properties. <code>LineItems<\/code> uses an initializer (<code>= new()<\/code>) to set the default value, and its setter invalidates the <code>Total<\/code> cache by setting it to null directly \u2014 which is valid because <code>Total<\/code> is declared as <code>decimal?<\/code>. The cache-and-compute logic in <code>Total<\/code>&#8216;s getter is unchanged in behavior but no longer requires a separately declared <code>_total<\/code> field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing to note: setting <code>Total = null<\/code> from within <code>LineItems<\/code>&#8216; setter assigns directly to <code>Total<\/code>&#8216;s backing field only if <code>Total<\/code> has no setter of its own with custom logic. Since <code>Total<\/code> has only a getter here, the assignment <code>Total = null<\/code> won&#8217;t compile \u2014 you&#8217;d still need either a setter on <code>Total<\/code> or a separate backing field for this specific invalidation pattern. This is a case where semi-auto properties get you most of the way but a full backing field still makes sense for the cache field specifically. It&#8217;s a good example of the feature not being a universal replacement.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 5: Init-Only Properties with Transformation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Since C# 9, <code>init<\/code> accessors have allowed properties to be set during object initialization but made immutable afterward. Semi-auto properties work naturally with <code>init<\/code>, and the combination is particularly useful when you want to transform or validate a value at construction time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Ticket<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">string<\/span> _reference;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Reference\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; _reference;\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">string<\/span>.IsNullOrWhiteSpace(<span class=\"hljs-keyword\">value<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"Reference cannot be blank.\"<\/span>, <span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            _reference = <span class=\"hljs-keyword\">value<\/span>.ToUpperInvariant();\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Ticket<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Reference\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">string<\/span>.IsNullOrWhiteSpace(<span class=\"hljs-keyword\">value<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"Reference cannot be blank.\"<\/span>, <span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            field = <span class=\"hljs-keyword\">value<\/span>.ToUpperInvariant();\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Clean and immutable. The <code>init<\/code> accessor runs once during object initialization \u2014 via a constructor, an object initializer, or a <code>with<\/code> expression on a record \u2014 validates the input, normalizes it to uppercase, and writes it to <code>field<\/code>. After that, the backing field is frozen and <code>Reference<\/code> becomes effectively read-only for the lifetime of the object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Combine this with the <code>required<\/code> modifier introduced in C# 11 and you have a tight, expressive pattern for value objects and domain entities:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Ticket<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> required <span class=\"hljs-keyword\">string<\/span> Reference\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">string<\/span>.IsNullOrWhiteSpace(<span class=\"hljs-keyword\">value<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"Reference cannot be blank.\"<\/span>, <span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            field = <span class=\"hljs-keyword\">value<\/span>.ToUpperInvariant();\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now <code>Reference<\/code> must be set during initialization, will be validated and normalized when it is, and can never be changed afterward. That&#8217;s a lot of correctness guarantees for very little code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Using <code>field<\/code> with <code>get<\/code>-Only and <code>init<\/code> Accessors<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Read-only properties are one of the more underappreciated tools in C#. They communicate intent clearly \u2014 this value is set once and doesn&#8217;t change \u2014 and they make types easier to reason about, especially in concurrent or functional-style code. Semi-auto properties work well in this space, but there are a few behavioral specifics worth understanding before you rely on them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Get-Only Semi-Auto Properties<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A get-only auto-property looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Id { <span class=\"hljs-keyword\">get<\/span>; }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The compiler generates a read-only backing field for it, and the only place that field can be assigned is in the constructor or via a field initializer. That rule carries over to get-only semi-auto properties:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Id\n{\n    <span class=\"hljs-keyword\">get<\/span> =&gt; field.ToUpperInvariant();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This compiles. The backing field for <code>Id<\/code> is still read-only \u2014 it can only be assigned in the constructor or via an initializer \u2014 and the getter applies a transformation on the way out. You&#8217;re not storing the uppercased value; you&#8217;re storing whatever was assigned in the constructor and uppercasing it on every read. Whether that&#8217;s what you want depends on the situation, but it&#8217;s a valid and useful pattern for lightweight formatting that doesn&#8217;t warrant caching.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s what constructor assignment looks like with a get-only semi-auto property:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Event<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Name\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field.Trim();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">Event<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">string<\/span> name<\/span>)<\/span>\n    {\n        Name = name; <span class=\"hljs-comment\">\/\/ assigns directly to the backing field<\/span>\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Notice that the constructor assigns to <code>Name<\/code>, not to a backing field named <code>_name<\/code>. The compiler allows this for get-only properties within the constructor body, exactly as it does for get-only auto-properties. The assignment bypasses the getter \u2014 it goes straight to the synthesized backing field \u2014 so the trimming in the getter has no effect at assignment time. It only runs when the property is read.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is an important behavioral detail: <strong>accessor logic only runs when the accessor runs<\/strong>. An assignment in a constructor goes directly to <code>field<\/code>, not through any setter you may or may not have defined. If you need your normalization or validation logic to run at construction time too, you need to either apply it explicitly in the constructor or use an <code>init<\/code> accessor instead.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Get-Only with Field Initializers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can combine a get-only semi-auto property with a field initializer just as you would with an auto-property:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Session<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> Guid SessionId\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n    } = Guid.NewGuid();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The initializer runs before the constructor body, pre-populating <code>field<\/code> with a new <code>Guid<\/code>. The property is read-only from that point on. This is functionally identical to <code>public Guid SessionId { get; } = Guid.NewGuid();<\/code> except that you now have the option to add getter logic around <code>field<\/code> if you ever need to. In this particular example the getter is trivial, but it&#8217;s easy to imagine adding formatting or transformation later without having to restructure the property.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>init<\/code> Accessors and <code>field<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">We touched on <code>init<\/code> in the previous section, but it&#8217;s worth going deeper here because the interaction between <code>init<\/code> and <code>field<\/code> has a few nuances that matter in practice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An <code>init<\/code> accessor behaves like a <code>set<\/code> accessor during object initialization and like no accessor at all afterward. From the compiler&#8217;s perspective, <code>field<\/code> inside an <code>init<\/code> accessor is writable during the initialization phase and then implicitly read-only once the object is fully constructed. This means you get the same guarantees as a get-only property \u2014 immutability after construction \u2014 but with the ability to set the value via object initializer syntax rather than only through a constructor.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Address<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Street\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init =&gt; field = <span class=\"hljs-keyword\">value<\/span>.Trim();\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> City\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init =&gt; field = <span class=\"hljs-keyword\">value<\/span>.Trim();\n    }\n}\n\n<span class=\"hljs-comment\">\/\/ Usage:<\/span>\n<span class=\"hljs-keyword\">var<\/span> address = <span class=\"hljs-keyword\">new<\/span> Address\n{\n    Street = <span class=\"hljs-string\">\"  123 Main St  \"<\/span>,\n    City = <span class=\"hljs-string\">\"  Springfield  \"<\/span>\n};\n\nConsole.WriteLine(address.Street); <span class=\"hljs-comment\">\/\/ \"123 Main St\"<\/span>\nConsole.WriteLine(address.City);   <span class=\"hljs-comment\">\/\/ \"Springfield\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The trimming happens in the <code>init<\/code> accessor when the object is initialized, and the cleaned values are what get stored in <code>field<\/code>. After that, neither <code>Street<\/code> nor <code>City<\/code> can be reassigned.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Combining <code>get<\/code> and <code>init<\/code> with Separate Logic<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">One pattern that becomes genuinely expressive with semi-auto properties is having distinct logic in both the <code>get<\/code> and <code>init<\/code> accessors:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Sku\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field.ToUpperInvariant();\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">string<\/span>.IsNullOrWhiteSpace(<span class=\"hljs-keyword\">value<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"SKU cannot be blank.\"<\/span>, <span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            field = <span class=\"hljs-keyword\">value<\/span>.Trim();\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Here the <code>init<\/code> accessor validates and trims the input before storing it, and the <code>get<\/code> accessor uppercases on every read. You&#8217;re storing a trimmed-but-original-case value and presenting it as uppercase. Whether you&#8217;d prefer to store it uppercase and present it as-is is a design decision \u2014 the point is that <code>field<\/code> gives you independent control over what gets stored and what gets returned, without any separately declared backing field.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>required<\/code> and <code>init<\/code> Together<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Combining <code>required<\/code> with an <code>init<\/code> semi-auto property gives you a pattern that enforces construction-time assignment, validates the input, and guarantees immutability \u2014 all in one property declaration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Customer<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> required <span class=\"hljs-keyword\">string<\/span> Email\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">value<\/span>.Contains(<span class=\"hljs-string\">'@'<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"Invalid email address.\"<\/span>, <span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            field = <span class=\"hljs-keyword\">value<\/span>.ToLowerInvariant();\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The <code>required<\/code> modifier means the compiler will enforce that <code>Email<\/code> is set in any object initializer that constructs a <code>Customer<\/code>. The <code>init<\/code> accessor validates and normalizes the value when it&#8217;s set. And once construction is complete, <code>Email<\/code> is frozen. You get compiler enforcement, runtime validation, and immutability, and the whole thing fits in eight lines with no separate backing field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This combination \u2014 <code>required<\/code>, <code>init<\/code>, and <code>field<\/code> \u2014 is probably the most practically useful pattern the feature enables for anyone building domain models, DTOs, or value objects. It&#8217;s worth having in your toolkit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Attribute Targeting: Applying Attributes to the Synthesized Backing Field<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One of the quieter pain points with auto-properties has always been attributes. Specifically: what do you do when you need to apply an attribute to the backing field rather than the property itself?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With a full property and an explicit backing field, this is trivial \u2014 you just decorate the field directly. With an auto-property, the backing field is compiler-generated and invisible, so you have to use the <code>field:<\/code> attribute target to reach it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\">&#91;<span class=\"hljs-meta\">field: NonSerialized<\/span>]\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> CachedValue { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">set<\/span>; }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This syntax has existed since C# was young, but it&#8217;s always felt a little awkward because it requires you to know that the compiler generated a field, know its target specifier, and trust that the attribute will land in the right place. More importantly, it only works for auto-properties \u2014 the moment you converted to a full property with an explicit backing field, you&#8217;d go back to decorating the field directly and the <code>field:<\/code> target syntax became irrelevant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties change this. Because the backing field is still compiler-generated even when you write custom accessor logic, the <code>field:<\/code> attribute target remains the correct and only way to decorate it. This actually makes the attribute story for semi-auto properties more consistent than it&#8217;s ever been for auto-properties, because now the same syntax works across a much broader range of property shapes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The <code>field:<\/code> Target Specifier<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The syntax is the same as it was for auto-properties:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserSession<\/span>\n{\n    &#91;<span class=\"hljs-meta\">field: NonSerialized<\/span>]\n    <span class=\"hljs-keyword\">public<\/span> DateTime LastAccessed\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The <code>[field: NonSerialized]<\/code> attribute is applied to the synthesized backing field, not to the property itself. The property remains visible and serializable as far as property-level serialization is concerned \u2014 it&#8217;s specifically the field that gets the attribute. This distinction matters depending on which serialization mechanism you&#8217;re using and whether it inspects fields, properties, or both.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Common Use Cases<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>JSON serialization with <code>System.Text.Json<\/code> or Newtonsoft.Json<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re using a serializer that can be configured to serialize fields as well as properties, you may want to exclude the backing field explicitly to avoid double-serialization:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ApiResponse<\/span>\n{\n    &#91;<span class=\"hljs-meta\">field: System.Text.Json.Serialization.JsonIgnore<\/span>]\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> RawPayload\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>?.Trim();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In most <code>System.Text.Json<\/code> configurations you won&#8217;t need this because field serialization is opt-in, but in Newtonsoft.Json with <code>MemberSerialization.Fields<\/code> or similar settings, it becomes relevant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Debugger visibility<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The synthesized backing field will show up in the debugger&#8217;s locals and watch windows under a compiler-generated name \u2014 something like <code>&lt;RawPayload&gt;k__BackingField<\/code>. If you&#8217;re debugging a type with several semi-auto properties and the backing fields are cluttering your watch window, you can suppress them:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ViewModel<\/span>\n{\n    &#91;<span class=\"hljs-meta\">field: System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)<\/span>]\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Title\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field ?? <span class=\"hljs-string\">\"Untitled\"<\/span>;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is the same technique used by auto-property backing fields internally \u2014 the compiler applies <code>DebuggerBrowsable(Never)<\/code> to its generated fields automatically. With semi-auto properties, it doesn&#8217;t do this by default, so if debugger cleanliness matters to you, you can apply it manually.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>ThreadStatic<\/code> and <code>SkipLocalsInit<\/code><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For more advanced scenarios, you can apply attributes like <code>[field: ThreadStatic]<\/code> to make the backing field thread-local, though this is an unusual pattern for a property and you should be deliberate about it. Similarly, <code>[field: SkipLocalsInit]<\/code> is technically applicable but unlikely to be meaningful in practice for most property patterns.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What You Cannot Do<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>field:<\/code> target only applies to the synthesized backing field. You cannot use it to apply attributes to the property&#8217;s getter or setter \u2014 those have their own target specifiers (<code>get:<\/code> and <code>set:<\/code> or <code>return:<\/code> for the getter&#8217;s return value, though these are rarely used in practice). Mixing up these targets is a compile-time error, so the compiler will catch it early.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s also worth noting that the <code>field:<\/code> target only works when there is a synthesized backing field to target \u2014 meaning the property must be an auto-property or a semi-auto property. Applying <code>[field: ...]<\/code> to a computed property with no <code>field<\/code> reference and no auto-accessor will produce a compiler warning or error depending on the context, because there&#8217;s no backing field for the attribute to land on.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A Practical Recommendation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re migrating auto-properties to semi-auto properties and those auto-properties already had <code>[field: ...]<\/code> attributes on them, the good news is that your attribute declarations don&#8217;t need to change at all. The <code>field:<\/code> target specifier works identically on both. The migration is purely a matter of adding accessor logic and switching from implicit <code>get<\/code>\/<code>set<\/code> to explicit bodies \u2014 the attribute targeting layer is completely unaffected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. <code>field<\/code> in Structs, Records, and Record Structs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So far all of our examples have used classes, which is where most developers will reach for semi-auto properties first. But <code>field<\/code> works across all type kinds in C#, and each one has its own behavioral nuances worth understanding before you use the feature in a struct or record context.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Structs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Structs have always had a slightly awkward relationship with properties. Before C# 10, a struct couldn&#8217;t even have auto-properties with custom logic in certain contexts without running into definite assignment issues. Things have improved considerably since then, but structs still carry rules that classes don&#8217;t, and semi-auto properties surface a couple of them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most important rule: <strong>structs are value types, and mutation through a property on a copied struct won&#8217;t affect the original<\/strong>. This isn&#8217;t specific to semi-auto properties \u2014 it&#8217;s a fundamental struct behavior \u2014 but it&#8217;s worth stating explicitly because <code>field<\/code> can make property-based mutation look deceptively clean:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">struct<\/span> Temperature\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> Celsius\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = Math.Round(<span class=\"hljs-keyword\">value<\/span>, <span class=\"hljs-number\">2<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This works exactly as you&#8217;d expect when you&#8217;re working with a direct reference to the struct. Where it bites you is in scenarios like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\">Temperature&#91;] readings = <span class=\"hljs-keyword\">new<\/span> Temperature&#91;<span class=\"hljs-number\">5<\/span>];\nreadings&#91;<span class=\"hljs-number\">0<\/span>].Celsius = <span class=\"hljs-number\">98.6<\/span>; <span class=\"hljs-comment\">\/\/ fine, modifies array element directly<\/span>\n\nTemperature t = readings&#91;<span class=\"hljs-number\">0<\/span>];\nt.Celsius = <span class=\"hljs-number\">100.0<\/span>; <span class=\"hljs-comment\">\/\/ modifies the copy, not readings&#91;0]<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Nothing about this behavior is new or specific to <code>field<\/code> \u2014 it&#8217;s how structs have always worked. But because semi-auto properties make property logic look so lightweight and clean, it&#8217;s easy to forget you&#8217;re working with a value type and accidentally mutate a copy. Keep your usual struct discipline in place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Definite assignment in structs<\/strong> is the other area to watch. In a struct, the compiler requires that all fields be definitely assigned before the constructor completes. With auto-properties, the compiler could reason about this easily because it knew the property and its backing field were in a one-to-one relationship. With semi-auto properties, the same relationship holds \u2014 assigning to a semi-auto property in a constructor assigns directly to the synthesized backing field \u2014 so definite assignment works as expected:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">struct<\/span> Point\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> X\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> Y\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">Point<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">double<\/span> x, <span class=\"hljs-keyword\">double<\/span> y<\/span>)<\/span>\n    {\n        X = x; <span class=\"hljs-comment\">\/\/ assigns to backing field for X<\/span>\n        Y = y; <span class=\"hljs-comment\">\/\/ assigns to backing field for Y<\/span>\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The compiler is satisfied that both backing fields are assigned in the constructor. If you forget to assign one, you&#8217;ll get the same definite assignment error you&#8217;d get with an explicit backing field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Readonly structs<\/strong> add another layer. In a <code>readonly struct<\/code>, all fields must be read-only, which means a semi-auto property in a readonly struct can only have a <code>get<\/code> accessor or an <code>init<\/code> accessor \u2014 not a mutable <code>set<\/code>. Attempting to write a <code>set<\/code> accessor that assigns to <code>field<\/code> in a readonly struct will produce a compiler error, because the synthesized backing field would need to be mutable to support it, which contradicts the <code>readonly<\/code> declaration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">readonly<\/span> <span class=\"hljs-keyword\">struct<\/span> ImmutablePoint\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> X\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field; <span class=\"hljs-comment\">\/\/ fine<\/span>\n        init =&gt; field = <span class=\"hljs-keyword\">value<\/span>; <span class=\"hljs-comment\">\/\/ fine<\/span>\n        <span class=\"hljs-comment\">\/\/ set =&gt; field = value; \/\/ compiler error<\/span>\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is consistent with how full properties work in readonly structs and shouldn&#8217;t be surprising, but it&#8217;s good to have it explicit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Records<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Records are where things get interesting, because the C# compiler generates a significant amount of code on your behalf for records \u2014 equality members, <code>GetHashCode<\/code>, <code>ToString<\/code> via <code>PrintMembers<\/code>, and for positional records, a deconstructor as well. Semi-auto properties interact with all of this generated code, and the interaction is largely seamless, but there are details worth knowing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Equality and <code>GetHashCode<\/code><\/strong> in records are generated based on the properties of the record, not its fields. This is an important distinction. The compiler-generated <code>Equals<\/code> and <code>GetHashCode<\/code> for a record call the property getters to get the values to compare and hash. That means if your semi-auto property&#8217;s getter applies a transformation \u2014 say, trimming or uppercasing \u2014 the equality comparison will use the transformed value, not whatever raw value is stored in <code>field<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> record Person\n{\n    <span class=\"hljs-keyword\">public<\/span> required <span class=\"hljs-keyword\">string<\/span> Name\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field.Trim();\n        init =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n}\n\n<span class=\"hljs-keyword\">var<\/span> a = <span class=\"hljs-keyword\">new<\/span> Person { Name = <span class=\"hljs-string\">\"Alice\"<\/span> };\n<span class=\"hljs-keyword\">var<\/span> b = <span class=\"hljs-keyword\">new<\/span> Person { Name = <span class=\"hljs-string\">\"Alice  \"<\/span> }; <span class=\"hljs-comment\">\/\/ trailing spaces<\/span>\n\nConsole.WriteLine(a == b); <span class=\"hljs-comment\">\/\/ true \u2014 both getters return \"Alice\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This might be exactly what you want \u2014 equality based on the normalized, meaningful value rather than the raw stored one. Or it might surprise you if you weren&#8217;t thinking about it. Either way, be deliberate: when you add getter logic to a record property, you&#8217;re also changing what equality means for that record.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>PrintMembers<\/code> and <code>ToString<\/code><\/strong> follow the same rule. The generated <code>ToString<\/code> for a record calls property getters, so the output will reflect your getter transformation rather than the raw stored value. For debugging and logging purposes this is usually the right behavior, but again, worth being conscious of.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>with<\/code> expressions<\/strong> are the one area where the interaction requires more care. A <code>with<\/code> expression creates a copy of a record with specified properties changed. Under the hood, the compiler uses the property&#8217;s <code>init<\/code> accessor (or setter, for mutable records) to apply the changed values to the copy. This means your <code>init<\/code> logic \u2014 validation, normalization, whatever you&#8217;ve put in there \u2014 runs again during a <code>with<\/code> expression:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> record Product\n{\n    <span class=\"hljs-keyword\">public<\/span> required <span class=\"hljs-keyword\">string<\/span> Name\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        init\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">string<\/span>.IsNullOrWhiteSpace(<span class=\"hljs-keyword\">value<\/span>))\n                <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentException(<span class=\"hljs-string\">\"Name cannot be blank.\"<\/span>);\n            field = <span class=\"hljs-keyword\">value<\/span>.Trim();\n        }\n    }\n}\n\n<span class=\"hljs-keyword\">var<\/span> original = <span class=\"hljs-keyword\">new<\/span> Product { Name = <span class=\"hljs-string\">\"Widget\"<\/span> };\n<span class=\"hljs-keyword\">var<\/span> renamed = original with { Name = <span class=\"hljs-string\">\"  Gadget  \"<\/span> }; <span class=\"hljs-comment\">\/\/ init runs, trims to \"Gadget\"<\/span>\n<span class=\"hljs-keyword\">var<\/span> invalid = original with { Name = <span class=\"hljs-string\">\"\"<\/span> }; <span class=\"hljs-comment\">\/\/ throws ArgumentException<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is generally the correct behavior \u2014 you want your invariants enforced on <code>with<\/code> just as they are on initial construction. But if your <code>init<\/code> accessor has side effects beyond simple validation and normalization, be aware that <code>with<\/code> will trigger them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Positional Records<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Positional record properties are auto-generated by the compiler from the primary constructor parameters:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">Point<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">double<\/span> X, <span class=\"hljs-keyword\">double<\/span> Y<\/span>)<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">These generated properties are standard auto-properties. You cannot directly attach <code>field<\/code>-based accessor logic to a positional property inline in the primary constructor syntax. If you need a positional record property to have semi-auto behavior, you need to override it explicitly in the record body:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">Point<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">double<\/span> X, <span class=\"hljs-keyword\">double<\/span> Y<\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> X\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        init =&gt; field = Math.Round(<span class=\"hljs-keyword\">value<\/span>, <span class=\"hljs-number\">4<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This overrides the compiler-generated <code>X<\/code> property with your semi-auto version. The positional deconstructor still works \u2014 it calls the property getter, which returns <code>field<\/code> \u2014 and equality and <code>ToString<\/code> use the getter as well, so the rounded value is what participates in all record-generated behavior. The <code>Y<\/code> property remains a plain auto-property since you didn&#8217;t override it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Record Structs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Record structs, introduced in C# 10, combine the value-type semantics of structs with the equality and <code>ToString<\/code> generation of records. Semi-auto properties in record structs follow both sets of rules discussed above: value-type mutation semantics apply, readonly record structs prohibit mutable setters, and record-generated equality calls property getters.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">readonly<\/span> record <span class=\"hljs-keyword\">struct<\/span> Coordinate\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> Latitude\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        init =&gt; field = Math.Clamp(<span class=\"hljs-keyword\">value<\/span>, <span class=\"hljs-number\">-90<\/span>, <span class=\"hljs-number\">90<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">double<\/span> Longitude\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        init =&gt; field = Math.Clamp(<span class=\"hljs-keyword\">value<\/span>, <span class=\"hljs-number\">-180<\/span>, <span class=\"hljs-number\">180<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is a clean and expressive pattern for small, immutable value objects. The clamping in the <code>init<\/code> accessors enforces geographic validity at construction time, the record generates correct equality and hashing based on the clamped values, and the <code>readonly<\/code> modifier on the record struct ensures nobody accidentally mutates a copy and wonders why the original didn&#8217;t change. It&#8217;s a good illustration of how several C# features layer together naturally once you understand what each one contributes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9. Thread Safety Considerations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Thread safety and properties have always had an uneasy relationship in C#. Auto-properties gave developers a convenient way to expose state, but they never made any promises about concurrent access \u2014 and semi-auto properties don&#8217;t change that. Before reaching for <code>field<\/code> in a multithreaded context, it&#8217;s worth being clear about what the feature does and doesn&#8217;t give you.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What <code>field<\/code> Doesn&#8217;t Change<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The synthesized backing field created by the compiler for a semi-auto property is a plain instance field. It gets no special treatment in terms of memory visibility, atomicity, or ordering. Concurrent reads and writes to it carry exactly the same risks as concurrent reads and writes to any other unsynchronized field \u2014 torn reads on types larger than the native word size, stale values due to CPU caching, and race conditions when your accessor logic involves more than a single memory operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is not a criticism of the feature. Thread safety is a cross-cutting concern that C# has deliberately left to developers and synchronization primitives rather than baking into the language&#8217;s property model. The point is simply that <code>field<\/code> doesn&#8217;t add anything new here, and you shouldn&#8217;t expect it to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consider this seemingly harmless semi-auto property:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RequestCounter<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> Count\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n        <span class=\"hljs-keyword\">set<\/span> =&gt; field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If multiple threads are incrementing <code>Count<\/code> via <code>counter.Count++<\/code>, you have a race condition. The increment operation is not atomic \u2014 it&#8217;s a read, an increment, and a write \u2014 and the fact that it happens through a semi-auto property rather than a plain field makes no difference whatsoever. The same issue exists with a full property backed by an explicit field, and with a plain public field.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lazy Initialization and Thread Safety<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lazy initialization is where thread safety concerns become most relevant for semi-auto properties, because the <code>??=<\/code> pattern discussed in section 5 is not thread-safe:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ReportGenerator<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;? Sections\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field ??= LoadSections();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If two threads call the <code>Sections<\/code> getter simultaneously and <code>field<\/code> is null for both of them, both will call <code>LoadSections()<\/code>. Whether that&#8217;s a problem depends on whether <code>LoadSections<\/code> has side effects and whether having two instances of the result floating around matters. In many cases \u2014 particularly for pure computation with no side effects \u2014 this kind of benign double-initialization is acceptable. But if <code>LoadSections<\/code> hits a database, fires an HTTP request, or has any observable side effect, you need a proper synchronization strategy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The standard solution for thread-safe lazy initialization in C# is <code>Lazy&lt;T&gt;<\/code>, and it pairs naturally with semi-auto properties:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ReportGenerator<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> Lazy&lt;List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;&gt; Sections\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n    } = <span class=\"hljs-keyword\">new<\/span> Lazy&lt;List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;&gt;(LoadSections, LazyThreadSafetyMode.ExecutionAndPublication);\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; <span class=\"hljs-title\">LoadSections<\/span>(<span class=\"hljs-params\"><\/span>)<\/span> { <span class=\"hljs-comment\">\/* ... *\/<\/span> }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This guarantees that <code>LoadSections<\/code> is called exactly once regardless of how many threads race to access <code>Sections<\/code> for the first time. The trade-off is that the property type is now <code>Lazy&lt;List&lt;string&gt;&gt;<\/code> rather than <code>List&lt;string&gt;<\/code>, so callers write <code>generator.Sections.Value<\/code> instead of <code>generator.Sections<\/code>. Whether that&#8217;s acceptable depends on your API design priorities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to hide the <code>Lazy&lt;T&gt;<\/code> from callers, a full property with an explicit <code>Lazy&lt;T&gt;<\/code> backing field is still the right tool:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ReportGenerator<\/span>\n{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">readonly<\/span> Lazy&lt;List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;&gt; _sections = \n        <span class=\"hljs-keyword\">new<\/span> Lazy&lt;List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;&gt;(LoadSections, LazyThreadSafetyMode.ExecutionAndPublication);\n\n    <span class=\"hljs-keyword\">public<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; Sections =&gt; _sections.Value;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; <span class=\"hljs-title\">LoadSections<\/span>(<span class=\"hljs-params\"><\/span>)<\/span> { <span class=\"hljs-comment\">\/* ... *\/<\/span> }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is one of those cases where a semi-auto property genuinely doesn&#8217;t help \u2014 the explicit backing field isn&#8217;t boilerplate here, it&#8217;s load-bearing infrastructure. Recognizing when <code>field<\/code> adds value and when a named backing field is still the right choice is part of using the feature well.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Volatile and Interlocked<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For simple scalar values where you need visibility guarantees across threads, <code>volatile<\/code> is the traditional answer. With an explicit backing field you can mark it <code>volatile<\/code> directly:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">volatile<\/span> <span class=\"hljs-keyword\">bool<\/span> _isRunning;\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">bool<\/span> IsRunning =&gt; _isRunning;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">With a semi-auto property, you can&#8217;t apply <code>volatile<\/code> to the synthesized backing field because <code>volatile<\/code> is not an attribute \u2014 it&#8217;s a field modifier, and field modifiers aren&#8217;t expressible through the <code>[field: ...]<\/code> attribute target syntax. This means that for any property where you need <code>volatile<\/code> semantics on the backing field, you need a full property with an explicit backing field. It&#8217;s a genuine limitation of the feature in multithreaded contexts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For atomic integer operations, <code>Interlocked<\/code> works at the level of the field reference, which isn&#8217;t accessible from outside the property body. You can use <code>Interlocked<\/code> operations inside an accessor:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RequestCounter<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> Count\n    {\n        <span class=\"hljs-keyword\">get<\/span> =&gt; field;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">Increment<\/span>(<span class=\"hljs-params\"><\/span>)<\/span> =&gt; Interlocked.Increment(<span class=\"hljs-keyword\">ref<\/span> field); <span class=\"hljs-comment\">\/\/ not valid<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This won&#8217;t work \u2014 <code>field<\/code> is only accessible inside property accessor bodies, not in methods. For <code>Interlocked<\/code>-based patterns you again need an explicit backing field. This is by design: <code>field<\/code> is intentionally scoped to accessors, and exposing the backing field reference to the broader class body would undermine the encapsulation the feature is meant to enforce.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A Practical Decision Framework<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The thread safety question for semi-auto properties comes down to a simple set of cases:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the property is read-only after construction and written only during initialization \u2014 which covers most immutable types, DTOs, and value objects \u2014 thread safety is a non-issue. Reads after construction are safe without synchronization as long as there&#8217;s a happens-before relationship between the write and the first read, which constructors and object initializers provide.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the property is mutable and accessed from multiple threads, and all you need is visibility rather than atomicity, you need <code>volatile<\/code> on the backing field \u2014 which means a full property with an explicit field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you need atomic operations or lazy initialization with strong thread safety guarantees, <code>Interlocked<\/code> and <code>Lazy&lt;T&gt;<\/code> respectively are the right tools, and both require explicit backing fields to use properly with properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the property is mutable and accessed from multiple threads and you&#8217;re using locks, the lock lives outside the property anyway, which means the property itself doesn&#8217;t need to do anything special \u2014 semi-auto or full, it makes no difference.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pattern that semi-auto properties handle well in concurrent code is the one that needs no special handling at all: immutable-after-construction state, set via <code>init<\/code> or in a constructor, read freely afterward. For everything else, the existing tools still apply, and the cases where those tools require an explicit backing field are real limitations worth being upfront about.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10. Gotchas, Limitations, and Known Edge Cases<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every language feature has its rough edges, and <code>field<\/code> is no exception. Most of the gotchas here are predictable once you understand the design constraints the C# team was working within, but a few are genuinely subtle and worth calling out explicitly so they don&#8217;t bite you in production code or a code review.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Name Shadowing Problem<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is the most commonly cited gotcha, and it&#8217;s a direct consequence of <code>field<\/code> being a contextual rather than reserved keyword.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you have a parameter or local variable named <code>field<\/code> inside a property accessor, it shadows the keyword. The compiler will prefer the local identifier over the contextual keyword, meaning <code>field<\/code> no longer refers to the synthesized backing field \u2014 it refers to your variable:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Mapper<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">string<\/span> Value\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">var<\/span> field = <span class=\"hljs-keyword\">value<\/span>.Trim(); <span class=\"hljs-comment\">\/\/ local variable named 'field'<\/span>\n            field = field;            <span class=\"hljs-comment\">\/\/ assigns local to itself, not to backing field!<\/span>\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This compiles without error, but it&#8217;s almost certainly not what you meant. The backing field never gets assigned, and <code>Value<\/code> will always return the default value for its type. The compiler does emit a warning when a local variable named <code>field<\/code> shadows the keyword inside an accessor, so you&#8217;ll be alerted \u2014 but warnings are easy to miss, especially in large codebases where warning counts are already high.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix is simple: rename the local variable to something that doesn&#8217;t conflict. But the real lesson is to treat <code>field<\/code> as mentally reserved inside property accessors, even though the language doesn&#8217;t enforce it. If your team adopts semi-auto properties widely, it&#8217;s worth adding a linting rule or code review note to flag local variables named <code>field<\/code> inside accessor bodies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This shadowing issue also applies to method parameters in expression-bodied accessors, though that scenario is less common since accessors don&#8217;t take parameters directly \u2014 <code>value<\/code> is the implicit parameter in setters and <code>init<\/code> accessors, and <code>field<\/code> isn&#8217;t a parameter name you&#8217;d reach for naturally in most cases.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Compiler-Generated Field Name<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When the compiler synthesizes a backing field for a semi-auto property, it gives that field a name following the same convention used for auto-property backing fields: <code>&lt;PropertyName&gt;k__BackingField<\/code>. So for a property named <code>Price<\/code>, the synthesized field will be named <code>&lt;Price&gt;k__BackingField<\/code> at the IL level.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This matters in a handful of scenarios:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Reflection.<\/strong> If you&#8217;re using reflection to enumerate a type&#8217;s fields \u2014 for serialization, mapping, testing, or any other purpose \u2014 the synthesized backing field will show up under its compiler-generated name. Code that filters fields by name or naming convention needs to account for this. The field name is not stable across compiler versions either, though in practice the convention hasn&#8217;t changed in many years.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Binary serialization.<\/strong> If you&#8217;re using <code>BinaryFormatter<\/code> (though you shouldn&#8217;t be in new code) or any other serialization mechanism that persists field names, renaming the property will change the backing field&#8217;s name and break deserialization of previously serialized data. This is the same issue that exists with auto-properties and is not new, but it&#8217;s easy to forget.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Source generators and analyzers.<\/strong> If you&#8217;re writing or maintaining a source generator or Roslyn analyzer that inspects property backing fields, you need to handle the case where the backing field is synthesized rather than explicitly declared. The <code>IPropertySymbol<\/code> in Roslyn exposes a <code>BackingField<\/code> property that returns the synthesized field symbol for both auto-properties and semi-auto properties, so the API surface is there \u2014 but generators that work by inspecting field declarations directly rather than through the property symbol will miss synthesized fields.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Initializer Bypass<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">We mentioned this briefly in section 4, but it deserves explicit emphasis because it&#8217;s a source of real bugs. <strong>Property initializers and constructor assignments bypass accessor logic.<\/strong> When you write:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span>\n{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Price\n    {\n        <span class=\"hljs-keyword\">get<\/span>;\n        <span class=\"hljs-keyword\">set<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt; <span class=\"hljs-number\">0<\/span>) <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(<span class=\"hljs-keyword\">value<\/span>));\n            field = <span class=\"hljs-keyword\">value<\/span>;\n        }\n    } = <span class=\"hljs-number\">-1<\/span>; <span class=\"hljs-comment\">\/\/ initializer<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The initializer <code>= -1<\/code> bypasses the setter entirely and writes directly to the backing field. You now have a <code>Product<\/code> with a <code>Price<\/code> of <code>-1<\/code> before any constructor logic runs, which directly violates the invariant your setter is trying to enforce. The same applies to assignments in constructors for get-only properties \u2014 the assignment goes directly to the backing field, not through any accessor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you need invariants enforced at initialization time, you have two options: validate in the constructor explicitly after setting the property, or use an <code>init<\/code> accessor with a setter-style body that runs validation, and set the property via an object initializer rather than a field initializer.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Interaction with <code>nameof<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Inside an accessor body, <code>nameof(field)<\/code> does not return <code>\"field\"<\/code>. It returns the compiler-generated field name, which is something like <code>&lt;Price&gt;k__BackingField<\/code>. This is almost certainly not what you want if you&#8217;re using <code>nameof<\/code> for error messages or logging:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">decimal<\/span> Price\n{\n    <span class=\"hljs-keyword\">get<\/span>;\n    <span class=\"hljs-keyword\">set<\/span>\n    {\n        <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">value<\/span> &lt; <span class=\"hljs-number\">0<\/span>)\n            <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> ArgumentOutOfRangeException(<span class=\"hljs-keyword\">nameof<\/span>(field)); <span class=\"hljs-comment\">\/\/ returns \"&lt;Price&gt;k__BackingField\"<\/span>\n        field = <span class=\"hljs-keyword\">value<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use <code>nameof(value)<\/code> for the parameter in setter exception messages, or the property name directly via <code>nameof(Price)<\/code>, rather than <code>nameof(field)<\/code>. This is a minor footgun but one that shows up in exception messages visible to developers and sometimes end users.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">No Cross-Accessor Field References Outside Properties<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>field<\/code> is strictly scoped to the accessor in which it appears. You cannot reference <code>field<\/code> from a method, a constructor body (beyond the implicit assignment behavior for get-only properties), or any other context outside a property accessor. This is by design, but it creates a genuine limitation for patterns where you want to reset or directly manipulate the backing value from within the class without going through the property&#8217;s accessor logic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if your setter validates and normalizes input, but you have an internal method that needs to set the backing value to a known-valid state without triggering that logic, you have no way to do that with a semi-auto property. You need a full property with an explicit backing field that your internal method can reach directly. This is a real and intentional trade-off \u2014 the encapsulation of <code>field<\/code> within the accessor body is what makes semi-auto properties safe and predictable \u2014 but it&#8217;s a limitation to be aware of.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>LangVersion<\/code> Mismatches and Silent Failures<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your project&#8217;s <code>LangVersion<\/code> is set below 13 and you write code that uses <code>field<\/code> inside a property accessor, the compiler won&#8217;t treat it as the backing field keyword \u2014 it will treat it as a regular identifier. Depending on context, this could mean it compiles successfully but does something completely wrong, or it produces an error because <code>field<\/code> as an identifier isn&#8217;t declared anywhere in scope.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The insidious case is when <code>field<\/code> happens to be a valid identifier in scope \u2014 a field or local variable of that name somewhere in the type \u2014 and the code compiles cleanly but behaves incorrectly because <code>field<\/code> is resolving to the wrong thing entirely. If you&#8217;re working in a multi-project solution with different <code>LangVersion<\/code> settings, or if you copy semi-auto property code into a project targeting an older language version, this is worth checking explicitly. A quick look at the project file&#8217;s <code>&lt;LangVersion&gt;<\/code> element will tell you whether the feature is available.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Serialization Frameworks and Field Discovery<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Several popular serialization frameworks \u2014 Newtonsoft.Json with certain settings, MessagePack, MemoryPack, and others \u2014 can be configured to serialize fields rather than or in addition to properties. When these frameworks discover fields via reflection, they will find the synthesized backing field for semi-auto properties and may attempt to serialize it, producing duplicate data in the output (once from the field, once from the property) or unexpected JSON keys based on the compiler-generated field name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The solution is the <code>[field: JsonIgnore]<\/code> or equivalent attribute targeting discussed in section 7. But the point here is that this isn&#8217;t just a theoretical concern \u2014 it&#8217;s an active gotcha for anyone migrating from auto-properties or full properties to semi-auto properties in a codebase that uses field-level serialization. Audit your serialization configuration and test your serialized output when migrating.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">One Thing <code>field<\/code> Simply Cannot Do<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s worth ending this section with a clear statement of the feature&#8217;s fundamental constraint: <code>field<\/code> gives you access to a single, compiler-managed backing store for the property. You cannot have two synthesized fields for one property, you cannot control the field&#8217;s type independently of the property type, and you cannot give the field a name that&#8217;s meaningful outside the property body. If any of those things matter for your use case, a full property with one or more explicitly declared backing fields is the right tool. Semi-auto properties are not trying to replace that pattern \u2014 they&#8217;re trying to eliminate the cases where you reach for it out of habit or necessity when you don&#8217;t actually need that level of control.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">11. Tooling and IDE Support<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A language feature is only as usable as the tooling around it, and it&#8217;s worth knowing what Visual Studio, JetBrains Rider, and the underlying Roslyn compiler actually offer when you&#8217;re working with semi-auto properties day to day. The short version is that support is solid, with a few rough edges you should know about before you lean on the tooling too heavily.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Roslyn Analyzer Support<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Roslyn compiler ships with a set of built-in analyzers that understand semi-auto properties at a semantic level. The most immediately useful of these is the analyzer that detects when a full property with an explicit backing field could be simplified to a semi-auto property. In Visual Studio and any editor running the Roslyn language server, this surfaces as a suggestion \u2014 typically a grey underline on the backing field declaration \u2014 with a quick fix action that performs the migration automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The suggested refactoring handles the straightforward cases well: it removes the backing field, rewrites the getter and setter to use <code>field<\/code>, and cleans up any now-unnecessary field references. For simple validation and normalization patterns, the automatic migration is reliable and worth using. For more complex patterns \u2014 particularly where the backing field is referenced from multiple places in the class, or where the field has attributes that need to be migrated to <code>[field: ...]<\/code> syntax \u2014 the analyzer is more conservative and may not offer the refactoring at all, which is the correct behavior. An analyzer that tries to be too clever about complex migrations causes more problems than it solves.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The shadowing warning discussed in section 10 \u2014 where a local variable named <code>field<\/code> inside an accessor shadows the keyword \u2014 is also surfaced by Roslyn as a compiler warning rather than just a style suggestion. This means it will appear in your build output and can be treated as a build error if your project uses <code>&lt;TreatWarningsAsErrors&gt;true&lt;\/TreatWarningsAsErrors&gt;<\/code>. For teams adopting semi-auto properties at scale, this is worth enabling or at least monitoring.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Visual Studio<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In Visual Studio 17.8 and later (the versions aligned with C# 13 preview and release), IntelliSense is fully aware of the <code>field<\/code> keyword inside property accessor bodies. It offers completion for <code>field<\/code>, shows the correct type information in hover tooltips, and navigates correctly when you use &#8220;Go to Definition&#8221; on <code>field<\/code> \u2014 though since there&#8217;s no explicit declaration to navigate to, it typically highlights the property declaration itself, which is the most useful thing it can do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The &#8220;Refactor&#8221; menu includes the migration action mentioned above when the analyzer detects a convertible pattern. You can also trigger it via the lightbulb menu (Ctrl+.) when your cursor is on the backing field or the property declaration. The rename refactoring is unaffected by semi-auto properties in any meaningful way \u2014 renaming the property renames the property, and the synthesized backing field&#8217;s compiler-generated name updates automatically since it&#8217;s derived from the property name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One area where Visual Studio&#8217;s support is still maturing is the debugger experience. The synthesized backing field shows up in the Locals and Autos windows under its compiler-generated name during debugging, which is cluttered and rarely useful. Applying <code>[field: DebuggerBrowsable(DebuggerBrowsableState.Never)]<\/code> as discussed in section 7 is currently a manual step \u2014 Visual Studio doesn&#8217;t automatically suppress synthesized backing fields the way it does for auto-property backing fields. This is a tooling gap that will likely close in a future Visual Studio update, but for now it&#8217;s worth being aware of if you do a lot of property-level debugging.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JetBrains Rider<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Rider&#8217;s support for semi-auto properties follows its usual pattern of being slightly ahead of Visual Studio on language feature adoption. The ReSharper-based engine that powers Rider&#8217;s code analysis understands <code>field<\/code> semantically and offers the same class of migration suggestions as Roslyn, with the addition of Rider&#8217;s more aggressive inspection suite which can catch a broader range of convertible patterns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rider&#8217;s parameter information and type inference hints work correctly for <code>field<\/code> \u2014 hovering over <code>field<\/code> in an accessor body shows the inferred type, and the inline type hint feature (if you have it enabled) displays the type next to <code>field<\/code> in the editor, which is useful when the property type is a long generic name and you want a quick reminder of what you&#8217;re working with without scrolling to the property declaration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The debugger integration in Rider handles the synthesized backing field slightly more gracefully than Visual Studio out of the box, grouping compiler-generated members separately in the debug view rather than mixing them with explicitly declared fields. This doesn&#8217;t eliminate the noise entirely, but it makes it easier to find what you&#8217;re looking for during a debugging session.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What the Decompiler Reveals<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you open a compiled assembly containing semi-auto properties in ILSpy, dnSpy, dotPeek, or the decompiler built into Rider, you&#8217;ll see the synthesized backing field exposed as a regular private field with its compiler-generated name. The decompiler will typically round-trip the code back to a full property with an explicit backing field rather than reconstructing the semi-auto syntax, because the compiled IL contains no metadata indicating that the property was originally written as a semi-auto property \u2014 that information exists only in source.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is worth knowing for two reasons. First, if you&#8217;re inspecting third-party code in a decompiler and see a full property with a <code>k__BackingField<\/code>-named field, it might have been written as a semi-auto property originally \u2014 you can&#8217;t tell from the IL alone. Second, the decompiled output gives you a useful sanity check on what the compiler is actually generating. For most semi-auto properties, the decompiled output is identical to what you would have written as a full property before the feature existed, which confirms that there&#8217;s no runtime overhead or behavioral difference \u2014 it&#8217;s purely a source-level convenience.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For IL-level inspection, the synthesized backing field is marked with <code>[CompilerGenerated]<\/code> just like auto-property backing fields, which is how tools and frameworks can distinguish synthesized fields from explicitly declared ones when they need to. If you&#8217;re writing tooling that inspects assemblies, filtering on <code>[CompilerGenerated]<\/code> is the right approach rather than relying on naming conventions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>dotnet format<\/code> and EditorConfig<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">At the time of writing, <code>dotnet format<\/code> doesn&#8217;t enforce any specific style rules around semi-auto property usage \u2014 it won&#8217;t automatically migrate full properties to semi-auto properties as part of a format pass. Style preferences around when to use semi-auto properties vs. full properties are currently expressed through Roslyn analyzer severity settings in <code>.editorconfig<\/code> rather than through formatting rules. If your team wants to enforce consistent usage, the place to configure that is the analyzer severity for the relevant diagnostic IDs, which you can set to <code>suggestion<\/code>, <code>warning<\/code>, or <code>error<\/code> depending on how strictly you want to enforce the pattern.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">12. When to Use Semi-Auto Properties vs. Full Properties<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After nine sections of detail, it&#8217;s worth stepping back and answering the practical question every developer will eventually ask standing in front of a property declaration: <em>should I use <code>field<\/code> here, or just write a full property?<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The honest answer is that for most cases the decision is obvious once you&#8217;ve internalized what the feature is for. Semi-auto properties solve a specific problem \u2014 compiler-managed backing storage with custom accessor logic \u2014 and the cases where they&#8217;re the right tool are easy to recognize. The cases where a full property is still warranted are equally clear, and it&#8217;s important not to reach for <code>field<\/code> out of novelty when a named backing field is genuinely the better choice.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Use Semi-Auto Properties When<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You need one thing from a backing field: storage.<\/strong> If the only reason you&#8217;d declare a private backing field is to have somewhere to put the value, and everything else about the property is standard get\/set or get\/init behavior with some logic wrapped around it, <code>field<\/code> is the right choice. The field name adds no value, the declaration adds no clarity, and removing both makes the code strictly easier to read.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The logic is self-contained within the accessor.<\/strong> Validation, normalization, trimming, clamping, coalescing \u2014 any logic that lives entirely inside the getter or setter without needing to reach outside the property \u2014 is a natural fit. The encapsulation that <code>field<\/code> enforces, where the backing store is only accessible through the accessors, is a feature rather than a limitation in these cases. It guarantees that your accessor logic is the only path to the stored value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You&#8217;re working with immutable or init-only properties.<\/strong> The combination of <code>required<\/code>, <code>init<\/code>, and <code>field<\/code> is probably the most compelling use case for the feature in everyday application code. Domain entities, value objects, DTOs with validation \u2014 anything that needs to be set once, validated on the way in, and immutable afterward is an ideal candidate. The pattern is expressive, the intent is clear, and it requires no supporting infrastructure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You&#8217;re migrating an existing auto-property that grew a small amount of logic.<\/strong> This is the most common real-world trigger. You had a clean <code>{ get; set; }<\/code> and a new requirement arrived \u2014 a range check, a default value, a normalization step. Previously your only option was a full migration to a backing field. Now you can add the logic you need without restructuring the property at all. If the Roslyn analyzer offers you the migration automatically, take it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Use a Full Property When<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The backing field needs to be accessed from outside the property&#8217;s accessors.<\/strong> If a method, constructor, or other property in the same type needs to read or write the backing value directly \u2014 bypassing the property&#8217;s accessor logic \u2014 you need a named field. There&#8217;s no workaround here: <code>field<\/code> is strictly accessor-scoped, and that&#8217;s not going to change. Any pattern involving <code>Interlocked<\/code>, direct field manipulation for performance reasons, or internal reset logic that bypasses validation falls into this category.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You need more than one backing value.<\/strong> A property that computes its result from two private fields, or that maintains both a raw and a processed version of its value, needs explicit field declarations. <code>field<\/code> gives you exactly one synthesized backing store per property, and you can&#8217;t get around that limit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You need <code>volatile<\/code> on the backing field.<\/strong> As covered in section 9, <code>volatile<\/code> is a field modifier rather than an attribute, so it can&#8217;t be applied through the <code>[field: ...]<\/code> target syntax. Any property in a multithreaded context that requires <code>volatile<\/code> semantics needs a full property with an explicit backing field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The backing field type needs to differ from the property type.<\/strong> The synthesized <code>field<\/code> always has the same type as the property. If you&#8217;re storing a <code>Lazy&lt;T&gt;<\/code> behind a <code>T<\/code>-typed property, or an <code>int<\/code> behind an <code>enum<\/code>-typed property, or anything else where the stored type and the exposed type diverge, you need an explicit backing field with the correct type.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The field has meaningful semantics beyond just storing the property value.<\/strong> If the backing field participates in serialization in a way that requires careful naming, or if it needs to be a specific named member for interop reasons, or if its existence as a named class member is itself meaningful to the design, an explicit declaration communicates that intentionality. Compiler-generated fields are implementation details; named fields are part of the type&#8217;s structure.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Rule of Thumb<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s a simple test that covers the majority of cases: <strong>if you would name the backing field <code>_propertyName<\/code> and use it for nothing other than storing and returning the property&#8217;s value, use <code>field<\/code>. If you&#8217;d name it something else, or use it for anything beyond simple storage, use a full property.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The naming test works because a backing field named <code>_price<\/code> for a property named <code>Price<\/code> is purely mechanical \u2014 it exists because the language required it, not because the name or the explicit declaration adds anything. That&#8217;s exactly the case <code>field<\/code> is designed to eliminate. A backing field named <code>_cachedResult<\/code>, <code>_lazyLoader<\/code>, <code>_rawBytes<\/code>, or anything other than a straightforward underscore-prefixed version of the property name is carrying semantic weight that deserves to be explicit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties are a quality-of-life improvement for a specific, common pattern. They&#8217;re not a replacement for full properties, and treating them as a default you apply everywhere will lead to code that works but reaches for <code>field<\/code> in cases where a named backing field would communicate intent more clearly. Use the feature where it genuinely simplifies things, and reach for a full property without hesitation when the situation calls for it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">13. Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Semi-auto properties and the <code>field<\/code> keyword are a small addition to C# in the grand scheme of language evolution. There&#8217;s no new runtime behavior, no new type system concept, no shift in how you think about object-oriented design. What there is, is the elimination of a specific piece of friction that C# developers have been working around since auto-properties were introduced in 2007.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That friction \u2014 the all-or-nothing choice between a convenient auto-property and a verbose full property \u2014 was never catastrophic. Developers wrote the backing fields, named them carefully, kept them in sync, and moved on. But the cost was real: noise in the codebase, surface area for bugs, and a subtle pressure against adding validation or normalization logic because the structural overhead felt disproportionate to the change.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>field<\/code> removes that pressure. The backing field is still there \u2014 the compiler generates it \u2014 but you don&#8217;t see it, name it, or maintain it. You write the logic you actually care about, and the rest is handled for you. The result is code that more directly expresses intent, with fewer moving parts between what you write and what you mean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The feature lands especially well in combination with patterns C# has been building toward for several versions: <code>init<\/code> accessors from C# 9, <code>required<\/code> members from C# 11, and the broader push toward more expressive, less ceremonial type declarations that has characterized the language&#8217;s recent evolution. Put those together and you have a genuinely compelling model for immutable-by-default types with construction-time validation \u2014 domain entities, value objects, and DTOs that enforce their own invariants without frameworks or base classes to lean on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If there&#8217;s one thing to take away from this tutorial, it&#8217;s the rule of thumb from the previous section: if you&#8217;d name the backing field <code>_propertyName<\/code> and use it for nothing other than storing the property&#8217;s value, <code>field<\/code> is the right call. Everything else stays a full property. That simple test will get you to the right answer in the vast majority of cases, and the rest of the nuance \u2014 thread safety, struct semantics, record equality, attribute targeting \u2014 is there when you need it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C# continues to be a language that takes its existing user base seriously, adding expressiveness without breaking what came before. Semi-auto properties are a good example of that philosophy done well: a pragmatic, backward-compatible improvement to a pattern that every C# developer has written hundreds of times. It won&#8217;t change how you think about the language, but it will quietly make your code a little cleaner, one property at a time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Introduction Auto-properties were a welcome addition back in C# 3.0. Instead of declaring a private backing field, writing a getter that returns it, and writing a setter that assigns to it, you could collapse all of that into a single line. Clean, fast, done. But that convenience came with a hard ceiling. The moment [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[8,4],"tags":[],"class_list":["post-2280","post","type-post","status-publish","format-standard","category-csharp","category-programming-languages","entry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Semi-Auto Properties and the field Keyword in C#<\/title>\n<meta name=\"description\" content=\"The name &quot;semi-auto property&quot; isn&#039;t an official C# specification term \u2014 it&#039;s the shorthand the community and the C# team landed on to describe what this feature actually is\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Semi-Auto Properties and the field Keyword in C#\" \/>\n<meta property=\"og:description\" content=\"The name &quot;semi-auto property&quot; isn&#039;t an official C# specification term \u2014 it&#039;s the shorthand the community and the C# team landed on to describe what this feature actually is\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-20T20:27:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-20T20:27:42+00:00\" \/>\n<meta name=\"author\" content=\"w3compadmin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"w3compadmin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"41 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"TechArticle\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/\"},\"author\":{\"name\":\"w3compadmin\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\"},\"headline\":\"Semi-Auto Properties and the field Keyword in C#\",\"datePublished\":\"2026-02-20T20:27:34+00:00\",\"dateModified\":\"2026-02-20T20:27:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/\"},\"wordCount\":9452,\"articleSection\":[\"C#\",\"Programming Languages\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/\",\"name\":\"Semi-Auto Properties and the field Keyword in C#\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#website\"},\"datePublished\":\"2026-02-20T20:27:34+00:00\",\"dateModified\":\"2026-02-20T20:27:42+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\"},\"description\":\"The name \\\"semi-auto property\\\" isn't an official C# specification term \u2014 it's the shorthand the community and the C# team landed on to describe what this feature actually is\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/semi-auto-properties-field-keyword-csharp\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Articles Home\",\"item\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Programming Languages\",\"item\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/programming-languages\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Semi-Auto Properties and the field Keyword in C#\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#website\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/\",\"name\":\"Developer Articles Hub\",\"description\":\"\",\"alternateName\":\"Developer Articles\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\",\"name\":\"w3compadmin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"contentUrl\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"caption\":\"w3compadmin\"},\"sameAs\":[\"http:\\\/\\\/w3computing.com\\\/articles\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Semi-Auto Properties and the field Keyword in C#","description":"The name \"semi-auto property\" isn't an official C# specification term \u2014 it's the shorthand the community and the C# team landed on to describe what this feature actually is","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/","og_locale":"en_US","og_type":"article","og_title":"Semi-Auto Properties and the field Keyword in C#","og_description":"The name \"semi-auto property\" isn't an official C# specification term \u2014 it's the shorthand the community and the C# team landed on to describe what this feature actually is","og_url":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/","article_published_time":"2026-02-20T20:27:34+00:00","article_modified_time":"2026-02-20T20:27:42+00:00","author":"w3compadmin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"w3compadmin","Est. reading time":"41 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/#article","isPartOf":{"@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/"},"author":{"name":"w3compadmin","@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561"},"headline":"Semi-Auto Properties and the field Keyword in C#","datePublished":"2026-02-20T20:27:34+00:00","dateModified":"2026-02-20T20:27:42+00:00","mainEntityOfPage":{"@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/"},"wordCount":9452,"articleSection":["C#","Programming Languages"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/","url":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/","name":"Semi-Auto Properties and the field Keyword in C#","isPartOf":{"@id":"https:\/\/www.w3computing.com\/articles\/#website"},"datePublished":"2026-02-20T20:27:34+00:00","dateModified":"2026-02-20T20:27:42+00:00","author":{"@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561"},"description":"The name \"semi-auto property\" isn't an official C# specification term \u2014 it's the shorthand the community and the C# team landed on to describe what this feature actually is","breadcrumb":{"@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.w3computing.com\/articles\/semi-auto-properties-field-keyword-csharp\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Articles Home","item":"https:\/\/www.w3computing.com\/articles\/"},{"@type":"ListItem","position":2,"name":"Programming Languages","item":"https:\/\/www.w3computing.com\/articles\/programming-languages\/"},{"@type":"ListItem","position":3,"name":"Semi-Auto Properties and the field Keyword in C#"}]},{"@type":"WebSite","@id":"https:\/\/www.w3computing.com\/articles\/#website","url":"https:\/\/www.w3computing.com\/articles\/","name":"Developer Articles Hub","description":"","alternateName":"Developer Articles","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.w3computing.com\/articles\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561","name":"w3compadmin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","url":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","contentUrl":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","caption":"w3compadmin"},"sameAs":["http:\/\/w3computing.com\/articles"]}]}},"featured_image_src":null,"featured_image_src_square":null,"author_info":{"display_name":"w3compadmin","author_link":"https:\/\/www.w3computing.com\/articles\/author\/w3compadmin\/"},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2280","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/comments?post=2280"}],"version-history":[{"count":7,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2280\/revisions"}],"predecessor-version":[{"id":2287,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2280\/revisions\/2287"}],"wp:attachment":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/media?parent=2280"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/categories?post=2280"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/tags?post=2280"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}