Tools: C# 14 extension blocks

Tools: C# 14 extension blocks

Source: Dev.to

Introduction ## Not supported prior to C# 14 ## Rules for Extension Operators ## Constrained Generic Operators ## Article source code ## Summary Learn about C# 14 extension blocks while targeting NET Core 9. This can be done by adding the following to the project file. What are convention extension methods? C# extension methods let you add new methods to existing types without modifying their source code or creating subclasses. They’re defined as static methods in a static class, with the first parameter prefixed by this to indicate the type being extended. Once in scope, they look and feel like native instance methods, which makes code more readable and expressive. extension blocks benefits Unlike the old syntax, extension blocks allow you to define extension properties, operators, and static members for a type. The "receiver" (the instance you are extending) is once for the entire block, making the code cleaner and more organized. Inside the block, members are declared like regular instance members. You don't need to repeat the this parameter for every method. Static operators can be used with C# 14, but not in preview. You can combine extension operators with Generic Math constraints (e.g., INumber) to perform arithmetic on collections of numbers. Using extension blocks means a developer has cleaner code, both for readability and maintenance, than before C#14. When using extension blocks in a team of developers, make sure to explain them to team members, especially developers who tend not to work with conventional extension methods. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: public static class StringExtensions { public static string CapitalizeFirstLetter(this string sender) => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: public static class StringExtensions { public static string CapitalizeFirstLetter(this string sender) => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } COMMAND_BLOCK: public static class StringExtensions { public static string CapitalizeFirstLetter(this string sender) => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } COMMAND_BLOCK: public static class StringExtensions { extension(string sender) { public string CapitalizeFirstLetter() => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: public static class StringExtensions { extension(string sender) { public string CapitalizeFirstLetter() => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } } COMMAND_BLOCK: public static class StringExtensions { extension(string sender) { public string CapitalizeFirstLetter() => string.IsNullOrEmpty(sender) ? sender : $"{char.ToUpper(sender[0])}{sender[1..].ToLower()}"; } } CODE_BLOCK: string firstName = "KAREN"; string lastName = "SMITH"; var fullName = $"{firstName.CapitalizeFirstLetter()} {lastName.CapitalizeFirstLetter()}"; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: string firstName = "KAREN"; string lastName = "SMITH"; var fullName = $"{firstName.CapitalizeFirstLetter()} {lastName.CapitalizeFirstLetter()}"; CODE_BLOCK: string firstName = "KAREN"; string lastName = "SMITH"; var fullName = $"{firstName.CapitalizeFirstLetter()} {lastName.CapitalizeFirstLetter()}"; COMMAND_BLOCK: public static class BoolExtensions { extension(bool value) { public string IsEmpty() => value switch { true => "Yes is empty", _ => "No is not empty" }; public string ToYesNo() => value switch { true => "Yes", _ => "No" }; } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: public static class BoolExtensions { extension(bool value) { public string IsEmpty() => value switch { true => "Yes is empty", _ => "No is not empty" }; public string ToYesNo() => value switch { true => "Yes", _ => "No" }; } } COMMAND_BLOCK: public static class BoolExtensions { extension(bool value) { public string IsEmpty() => value switch { true => "Yes is empty", _ => "No is not empty" }; public string ToYesNo() => value switch { true => "Yes", _ => "No" }; } } COMMAND_BLOCK: public static class EnumerableExtensions { // The generic T is defined at the block level extension<T>(IEnumerable<T> first) { // Extension Operator // Note: At least one parameter must match the extended type (IEnumerable<T>) public static IEnumerable<T> operator +(IEnumerable<T> left, IEnumerable<T> right) => left.Concat(right); // Extension Compound Assignment (C# 14 feature) // This allows 'collection += item' behavior public static IEnumerable<T> operator +(IEnumerable<T> left, T item) => left.Append(item); } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: public static class EnumerableExtensions { // The generic T is defined at the block level extension<T>(IEnumerable<T> first) { // Extension Operator // Note: At least one parameter must match the extended type (IEnumerable<T>) public static IEnumerable<T> operator +(IEnumerable<T> left, IEnumerable<T> right) => left.Concat(right); // Extension Compound Assignment (C# 14 feature) // This allows 'collection += item' behavior public static IEnumerable<T> operator +(IEnumerable<T> left, T item) => left.Append(item); } } COMMAND_BLOCK: public static class EnumerableExtensions { // The generic T is defined at the block level extension<T>(IEnumerable<T> first) { // Extension Operator // Note: At least one parameter must match the extended type (IEnumerable<T>) public static IEnumerable<T> operator +(IEnumerable<T> left, IEnumerable<T> right) => left.Concat(right); // Extension Compound Assignment (C# 14 feature) // This allows 'collection += item' behavior public static IEnumerable<T> operator +(IEnumerable<T> left, T item) => left.Append(item); } } CODE_BLOCK: var list1 = new[] { 1, 2 }; var list2 = new[] { 3, 4 }; var combined = list1 + list2; // Yields { 1, 2, 3, 4 } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var list1 = new[] { 1, 2 }; var list2 = new[] { 3, 4 }; var combined = list1 + list2; // Yields { 1, 2, 3, 4 } CODE_BLOCK: var list1 = new[] { 1, 2 }; var list2 = new[] { 3, 4 }; var combined = list1 + list2; // Yields { 1, 2, 3, 4 } COMMAND_BLOCK: public static class NumericExtensions { extension<T>(IEnumerable<T> source) where T : INumber<T> { public IEnumerable<T> OnlyPositive => source.Where(x => x > T.Zero); } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: public static class NumericExtensions { extension<T>(IEnumerable<T> source) where T : INumber<T> { public IEnumerable<T> OnlyPositive => source.Where(x => x > T.Zero); } } COMMAND_BLOCK: public static class NumericExtensions { extension<T>(IEnumerable<T> source) where T : INumber<T> { public IEnumerable<T> OnlyPositive => source.Where(x => x > T.Zero); } } - Static Context: Like standard operators, extension operators must be declared public static. - Identity Rule: One of the operator's parameters must be identity-convertible to the type being extended (e.g., if you extend T[], one parameter must be T[]). - Compound Assignments: C# 14 also introduces user-defined compound assignment operators (like +=), which can now be implemented as extensions.