{"id":58891,"date":"2025-11-17T10:05:00","date_gmt":"2025-11-17T18:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=58891"},"modified":"2025-11-17T10:05:00","modified_gmt":"2025-11-17T18:05:00","slug":"introducing-csharp-14","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-csharp-14\/","title":{"rendered":"Introducing C# 14"},"content":{"rendered":"<p>C# 14 ships with .NET 10. The highlight is new <code>extension<\/code> members, but there&#8217;s a lot more features that make your life as a developer more productive. And, we&#8217;ve added new features that enable some of the performance improvements you can experience in .NET 10. Read on for a tour of all the new features, and find links to dive deeper and start using these features today.<\/p>\n<p><iframe width=\"800\" height=\"450\" src=\"https:\/\/www.youtube.com\/embed\/xy-HzFp0pbA?si=XjEAI7q2qJm8yT5K\" allowfullscreen><\/iframe><\/p>\n<h2>Extension members<\/h2>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14#extension-members\">Extension members<\/a> are the headline feature of C# 14. The new syntax is fully compatible with existing extension methods. Extension members enables extension properties, extension operators, and static extension members.<\/p>\n<p>The following code shows an example extension block. The extension block contains two instance extensions followed by two static extensions for the same type. The receiver name, <code>source<\/code>, is optional if the extension only contains static extensions.<\/p>\n<pre><code class=\"language-csharp\">public static class EnumerableExtensions\n{\n \/\/ Instance-style extension members: 'source' is the receiver variable\n extension&lt;TSource&gt;(IEnumerable&lt;TSource&gt; source)\n {\n  \/\/ Extension property\n  public bool IsEmpty =&gt; !source.Any();\n\n  \/\/ Extension method (body elided for brevity)\n  public IEnumerable&lt;TSource&gt; Where(Func&lt;TSource, bool&gt; predicate)\n  {\n   \/\/ Implementation would filter 'source'\n   throw new NotImplementedException();\n  }\n\n  \/\/ Static extension property\n  public static IEnumerable&lt;TSource&gt; Identity =&gt; Enumerable.Empty&lt;TSource&gt;();\n\n  \/\/ Static user-defined operator provided as an extension\n  public static IEnumerable&lt;TSource&gt; operator +(\n   IEnumerable&lt;TSource&gt; left,\n   IEnumerable&lt;TSource&gt; right) =&gt; left.Concat(right);\n }\n}<\/code><\/pre>\n<p>Usage examples:<\/p>\n<pre><code class=\"language-csharp\">int[] data = ...;\n\/\/ access instance extension property:\nif (data.IsEmpty) { \/* ... *\/ }\n\n\/\/ Access static extension operator +\nvar combined = data + [ 4, 5 ];\n\n\/\/ Access static extension property:\nvar empty = IEnumerable&lt;int&gt;.Identity;<\/code><\/pre>\n<p>Because extension blocks are source and binary compatible with existing extension methods, you can migrate one method at a time. Dependent assemblies don&#8217;t need to be recompiled and continue to bind to the original symbol.<\/p>\n<p>You can learn more and explore extension members in the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/programming-guide\/classes-and-structs\/extension-methods\">C# Guide<\/a> and the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/keywords\/extension\"><code>extension<\/code> keyword article<\/a>. You can also read all the details on the feature design in the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-14.0\/extensions\">Extensions proposal<\/a>.<\/p>\n<h2>More productivity for you<\/h2>\n<p>This set of language features share a common goal: reduce the syntactic friction for everyday tasks so you can focus on domain logic instead of ceremony. They eliminate boilerplate, remove common conditional blocks, simplify lambda declarations, enhance partial types for source generators, and make <code>nameof<\/code> more expressive in generic scenarios. Individually each saves a few lines and more typing. Together they translate into cleaner code, fewer trivial identifiers, and code that communicates intent more cleanly.<\/p>\n<ul>\n<li><a href=\"#the-field-keyword\">The <code>field<\/code> keyword<\/a><\/li>\n<li><a href=\"#unbound-generic-types-and-nameof\">Unbound generic types and <code>nameof<\/code><\/a><\/li>\n<li><a href=\"#simple-lambda-parameters-with-modifiers\">Simple lambda parameters with modifiers<\/a><\/li>\n<li><a href=\"#null-conditional-assignment\">Null-conditional assignment<\/a><\/li>\n<li><a href=\"#partial-events-and-constructors\">Partial events and constructors<\/a><\/li>\n<\/ul>\n<h3>The <code>field<\/code> keyword<\/h3>\n<p>Most properties start life as simple auto\u2011implemented properties. Later you discover you need small bits of logic \u2014 null coalescing, clamping, simple normalization, or raising a guard \u2014 on just one accessor. Before C# 14 that requirement forced you to convert to a fully hand\u2011written backing field pattern:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\nprivate string _message = \"\";\npublic string Message\n{\n get =&gt; _message;\n init =&gt; _message = value \n           ?? throw new ArgumentNullException(nameof(value));\n}<\/code><\/pre>\n<p>The contextual <code>field<\/code> keyword creates a middle step on that evolution path: keep the auto\u2011property terseness, inject minimal logic only where needed, and let the compiler synthesize and name the backing storage. You add just the accessor body that needs logic and refer to the compiler\u2011generated storage via <code>field<\/code>:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\npublic string Message\n{\n get; \/\/ auto get\n init =&gt; field = value \n           ?? throw new ArgumentNullException(nameof(value));\n}<\/code><\/pre>\n<p>It&#8217;s a bridge between auto\u2011implemented and fully hand\u2011written properties: start with <code>public string Message { get; init; }<\/code>, then when you need a quick guard, convert only the accessor that requires code and use <code>field<\/code> instead of introducing a private member and duplicating a trivial getter. This pattern scales when many properties each require a one\u2011line check\u2014your class stays visually lightweight and diffs stay small. Another advantage of <code>field<\/code> is that it avoids creating a new named private field. All code in the type must use the property to access or modify the value of the property.<\/p>\n<p>This feature was available as a preview in .NET 9. The <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/keywords\/field\"><code>field<\/code> contextual keyword<\/a> is now generally available in C# 14 (see <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14#the-field-keyword\">what&#8217;s new<\/a>).<\/p>\n<h3>Unbound generic types and <code>nameof<\/code><\/h3>\n<p>Previously, to log or throw using just the generic type name you either hardcoded a string or used a closed constructed type:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\nvar listTypeName = nameof(List&lt;int&gt;); \/\/ \"List\"\n\/\/ or:\nconst string Expected = \"List\";<\/code><\/pre>\n<p>Now <code>nameof<\/code> accepts an unbound generic type. This feature removes the need to pick an arbitrary type argument just to retrieve the generic type&#8217;s name:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\nvar listTypeName = nameof(List&lt;&gt;); \/\/ \"List\"<\/code><\/pre>\n<p>This produces the generic type name once, without implying any specific instantiation. Learn more in the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/operators\/nameof\"><code>nameof<\/code> operator reference<\/a>.<\/p>\n<h3>Simple lambda parameters with modifiers<\/h3>\n<p>In earlier versions, parameter modifiers such as <code>out<\/code> in delegates required full type annotations on all parameters:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\ndelegate bool TryParse&lt;T&gt;(string text, out T value);\nTryParse&lt;int&gt; parse = (string text, out int result) =&gt; int.TryParse(text, out result);<\/code><\/pre>\n<p>Now you can keep the concise implicitly typed form while still using modifiers like <code>out<\/code>, <code>ref<\/code>, <code>in<\/code>, <code>scoped<\/code> on one or more parameters:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\nTryParse&lt;int&gt; parse = (text, out result) =&gt; int.TryParse(text, out result);<\/code><\/pre>\n<p>The parameter types are still inferred, preserving the concise syntax of the lambda expression. Learn more in the C# language reference section on <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/operators\/lambda-expressions#input-parameters-of-a-lambda-expression\">lambda expression parameter modifiers<\/a>. It keeps lambdas terse while still exposing flow semantics (<code>out<\/code>, <code>ref<\/code>, <code>in<\/code>, <code>scoped<\/code>).<\/p>\n<h3>Null-conditional assignment<\/h3>\n<p>Guarded assignments previously required an explicit null check:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\nif (customer is not null)\n{\n customer.Order = CreateOrder();\n customer.Total += CalculateIncrement();\n}<\/code><\/pre>\n<p>Now you can assign (and use compound assignment) directly with null-conditional operators on the left side of the assignment. The right side is evaluated only when the receiver of the assignment isn&#8217;t null:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\ncustomer?.Order = CreateOrder();\ncustomer?.Total += CalculateIncrement();<\/code><\/pre>\n<p>That trims indentation and visually centers the important work.\nThe feature integrates directly with the existing <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/operators\/member-access-operators#null-conditional-operators--and-\">null-conditional operators<\/a> so they can appear on the left side of an assignment. It evaluates the right-hand expression only when the receiver isn&#8217;t null, avoiding helper locals or duplicated checks. See <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14#null-conditional-assignment\">null-conditional assignment<\/a> and the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-14.0\/null-conditional-assignment\">feature specification<\/a>.<\/p>\n<h3>Partial events and constructors<\/h3>\n<p>Large generated or source\u2011generated partial types can now spread event and constructor logic across files, enabling generators or different files to contribute cleanly:<\/p>\n<pre><code class=\"language-csharp\">public partial class Widget(int size, string name) \/\/ defining declaration of primary ctor\n{\n public partial event EventHandler Changed; \/\/ declaring event declaration (field-like)\n}\n\npublic partial class Widget\n{\n public partial event EventHandler Changed \/\/ Defining declaration for event.\n {\n  add =&gt; _changed += value;\n  remove =&gt; _changed -= value;\n }\n\n private EventHandler? _changed;\n\n \/\/ Implementing declaration can add constructor body logic\n public Widget\n {\n  Initialize();\n }\n}<\/code><\/pre>\n<p>This separation enables new source generation scenarios (e.g., a generator supplies the defining members, user code supplies behavior, or vice-versa). It simplifies the manually authored logic. It remains more focused on the algorithms you write by hand.<\/p>\n<p>See the programming guide for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/programming-guide\/classes-and-structs\/constructors#partial-constructors\">partial constructors<\/a> and the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/keywords\/partial-member\"><code>partial<\/code> member reference<\/a> for syntax details.<\/p>\n<h2>More performance for your users<\/h2>\n<p>Many of the raw throughput wins you&#8217;ll see after upgrading to .NET 10 come from the runtime and BCL adopting new C# 14 capabilities. Core libraries already use these features so your apps often get faster even if you never write this syntax yourself. The <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-10\/\">.NET 10 performance improvements post<\/a> highlights span-heavy parsing, UTF-8 processing, and numeric routines that benefit. Two language additions in particular unlock cleaner, faster library implementations: <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14#implicit-span-conversions\">implicit span conversions<\/a> and <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14#user-defined-compound-assignment\">user-defined compound assignment<\/a>.<\/p>\n<h3>Implicit span conversions<\/h3>\n<p><code>Span&lt;T&gt;<\/code> \/ <code>ReadOnlySpan&lt;T&gt;<\/code> are central to allocation-free APIs. C# 14 adds implicit conversions among arrays, spans, and read-only spans so you write less ceremony and the JIT sees simpler call graphs. That translates into fewer temporary variables, fewer bounds checks, and more aggressive inlining in the framework (as described in the performance blog&#8217;s sections covering text and parsing micro-benchmarks).<\/p>\n<p>Earlier C# versions required code like the following:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\nstring line = ReadLine();\nReadOnlySpan&lt;char&gt; key = line.AsSpan(0, 5); \/\/ explicit AsSpan\nProcessKey(key);\n\nint[] buffer = GetBuffer();\nSpan&lt;int&gt; head = new(buffer, 0, 8); \/\/ explicit Span ctor\nAccumulate(head);<\/code><\/pre>\n<p>Now, you can write the following:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\nstring line = ReadLine();\nProcessKey(line[..5]);              \/\/ substring slice implicitly converts\n\nint[] buffer = GetBuffer();\nAccumulate(buffer[..8]);<\/code><\/pre>\n<p>Library authors exploit these conversions to remove helper locals and express slice intent inline. The benefits include fewer explicit <code>AsSpan<\/code> or constructor calls, clearer slicing intent that encourages span-friendly overloads, and framework optimizations that reduce allocations through broader zero-allocation paths. Learn more by reading the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-14.0\/first-class-span-types\">first-class span types spec<\/a>.<\/p>\n<h3>User defined compound assignment<\/h3>\n<p>High-performance numeric and vector types often accumulate values in tight loops. Without a dedicated compound assignment operator, code either repeated the left hand reference or created intermediate temporaries through an ordinary binary operator\u2014both patterns can inhibit certain JIT optimizations. C# 14 lets you declare a compound assignment operator (<code>+=<\/code>, <code>-=<\/code>, etc.) explicitly so the compiler dispatches directly to your implementation. Libraries taking advantage of this (for example, SIMD-friendly helpers referenced in the performance blog) to avoid extra temporaries and can expose more idiomatic APIs.<\/p>\n<p>Instead of this:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Before\nBigVector sum = BigVector.Zero;\nforeach (var v in values)\n{\n sum = sum.Add(v); \/\/ intermediate result each iteration\n}<\/code><\/pre>\n<p>After you provide a compound operator that can update the result in-place:<\/p>\n<pre><code class=\"language-csharp\">\/\/ After (C# 14)\nBigVector sum = BigVector.Zero;\nforeach (var v in values)\n{\n sum += v; \/\/ calls user-defined operator += directly\n}<\/code><\/pre>\n<p>Defining both the binary and compound operators:<\/p>\n<pre><code class=\"language-csharp\">public struct BigVector(float x, float y, float z)\n{\n public float X { get; private set =&gt; value = field; } = x;\n public float Y { get; private set =&gt; value = field; } = y;\n public float Z { get; private set =&gt; value = field; } = z;\n\n public static BigVector operator +(BigVector l, BigVector r)\n  =&gt; new(l.X + r.X, l.Y + r.Y, l.Z + r.Z);\n\n public void operator +=(BigVector r)\n {\n  X += r.X;\n  Y += r.Y;\n  Z += r.Z;\n }\n}<\/code><\/pre>\n<p>Details appear in the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/operators\/operator-overloading\">operator overloading article<\/a> in the C# guide and the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-14.0\/user-defined-compound-assignment\">compound assignment spec<\/a>. You should also consult the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/breaking-changes\/compiler%20breaking%20changes%20-%20dotnet%2010\">compiler breaking changes article<\/a> for potential issues. You might encounter issues regarding <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/breaking-changes\/compiler%20breaking%20changes%20-%20dotnet%2010#enumerablereverse\"><code>Enumerable.Reverse<\/code><\/a>.<\/p>\n<h2>Summary<\/h2>\n<p>That&#8217;s a quick tour of what we&#8217;ve <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-14\">delivered in C# 14<\/a>: new extensions, a number of features that make you more productive, and enhancements that make your C# programs perform better. Download .NET 10 and try it on your apps. Participate in ongoing discussions to continue to make C# a great language choice for you.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn what features are in C# 14, which ships as part of .NET 10.<\/p>\n","protected":false},"author":56654,"featured_media":58892,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,756],"tags":[7892,46,8087,58,8086,8088],"class_list":["post-58891","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-csharp","tag-dotnet-10","tag-c","tag-compound-assignment","tag-csharp","tag-extension","tag-null-conditional"],"acf":[],"blog_post_summary":"<p>Learn what features are in C# 14, which ships as part of .NET 10.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/58891","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/56654"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=58891"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/58891\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58892"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=58891"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=58891"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=58891"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}