diff --git a/docfx.json b/docfx.json index ae49b6563579d..5057b8604885e 100644 --- a/docfx.json +++ b/docfx.json @@ -59,7 +59,8 @@ "first-class-span-types.md", "simple-lambda-parameters-with-modifiers.md", "partial-events-and-constructors.md", - "null-conditional-assignment.md" + "null-conditional-assignment.md", + "extensions.md" ], "src": "_csharplang/proposals", "dest": "csharp/language-reference/proposals", @@ -510,7 +511,7 @@ "_csharplang/proposals/csharp-11.0/*.md": "09/30/2022", "_csharplang/proposals/csharp-12.0/*.md": "08/15/2023", "_csharplang/proposals/csharp-13.0/*.md": "10/31/2024", - "_csharplang/proposals/*.md": "04/04/2025", + "_csharplang/proposals/*.md": "04/08/2025", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "11/08/2022", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "11/08/2023", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "11/09/2024", @@ -691,6 +692,7 @@ "_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "Simple lambda parameters with modifiers", "_csharplang/proposals/partial-events-and-constructors.md": "Partial events and constructors", "_csharplang/proposals/null-conditional-assignment.md": "Null conditional assignment", + "_csharplang/proposals/extensions.md": "Extension members", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "C# compiler breaking changes since C# 11", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "C# compiler breaking changes since C# 12", @@ -817,6 +819,7 @@ "_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "This proposal allows lambda parameters to be declared with modifiers without requiring their type names. You can add modifiers like `ref` and `out` to lambda parameters without specifying their type.", "_csharplang/proposals/partial-events-and-constructors.md": "This proposal allows partial events and constructors to be declared in partial classes. The event and constructor can be split across class declarations.", "_csharplang/proposals/null-conditional-assignment.md": "This proposal allows the null conditional operator to be used for the destination of assignment expressions. This allows you to assign a value to a property or field only if the left side is not null.", + "_csharplang/proposals/extensions.md": "This proposal enables new kinds of extension members. These new extension members support extension properties, extension static members, including extension operators.", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "Learn about any breaking changes since the initial release of C# 11 and included in C# 12", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "Learn about any breaking changes since the initial release of C# 12 and included in C# 13", diff --git a/docs/core/whats-new/dotnet-10/overview.md b/docs/core/whats-new/dotnet-10/overview.md index a0108678bd5a3..175aa63443864 100644 --- a/docs/core/whats-new/dotnet-10/overview.md +++ b/docs/core/whats-new/dotnet-10/overview.md @@ -81,10 +81,10 @@ C# 14 introduces several new features and enhancements to improve developer prod - **Unbound generic support for `nameof`**: The `nameof` expression now supports unbound generic types, such as `List<>`, returning the name of the type without requiring type arguments. - **Implicit span conversions**: Introduces first-class support for `Span` and `ReadOnlySpan` with new implicit conversions, enabling more natural programming with these types. - **Modifiers on simple lambda parameters**: Allows parameter modifiers like `ref`, `in`, or `out` in lambda expressions without specifying parameter types. -- **Experimental feature - String literals in data section**: Enables emitting string literals as UTF-8 data into a separate section of the PE file, improving efficiency for certain scenarios. - **Partial events and constructors**: Adds support for partial instance constructors and partial events, complementing partial methods and properties introduced in C# 13. - **Extension members**: Extension methods now support static methods, instance properties, and static properties through `extension` blocks, enabling more flexible and powerful extensions. - **Null-conditional assignment**: Simplifies conditional assignments by allowing properties or fields to be updated only if the containing instance exists, using the `?.` operator. +- **Experimental feature - String literals in data section**: Enables emitting string literals as UTF-8 data into a separate section of the PE file, improving efficiency for certain scenarios. For more information, see [What's new in C# 14](../../../csharp/whats-new/csharp-14.md). diff --git a/docs/csharp/fundamentals/object-oriented/index.md b/docs/csharp/fundamentals/object-oriented/index.md index 6122f06bc4a26..976b99ff5b4de 100644 --- a/docs/csharp/fundamentals/object-oriented/index.md +++ b/docs/csharp/fundamentals/object-oriented/index.md @@ -1,7 +1,7 @@ --- title: "Classes, structs, and records" description: Describes the use of classes, structures (structs), and records in C#. -ms.date: 03/23/2022 +ms.date: 04/17/2025 helpviewer_keywords: - "structs [C#], about structs" - "records [C#], about records" @@ -14,17 +14,17 @@ helpviewer_keywords: --- # Overview of object oriented techniques in C\# -In C#, the definition of a type—a class, struct, or record—is like a blueprint that specifies what the type can do. An object is basically a block of memory that has been allocated and configured according to the blueprint. This article provides an overview of these blueprints and their features. The [next article in this series](objects.md) introduces objects. +In C#, the definition of a type—a class, struct, or record—is like a blueprint that specifies what the type can do. An object is basically a block of memory allocated and configured according to the blueprint. This article provides an overview of these blueprints and their features. The [next article in this series](objects.md) introduces objects. ## Encapsulation - *Encapsulation* is sometimes referred to as the first pillar or principle of object-oriented programming. A class or struct can specify how accessible each of its members is to code outside of the class or struct. Methods and variables that aren't intended to be used from outside of the class or assembly can be hidden to limit the potential for coding errors or malicious exploits. For more information, see the [Object-oriented programming](../tutorials/oop.md) tutorial. + *Encapsulation* is sometimes referred to as the first pillar or principle of object-oriented programming. A class or struct can specify how accessible each of its members is to code outside of the class or struct. Member not intended for consumers outside of the class or assembly are hidden to limit the potential for coding errors or malicious exploits. For more information, see the [Object-oriented programming](../tutorials/oop.md) tutorial. ## Members The *members* of a type include all methods, fields, constants, properties, and events. In C#, there are no global variables or methods as there are in some other languages. Even a program's entry point, the `Main` method, must be declared within a class or struct (implicitly when you use [top-level statements](../program-structure/top-level-statements.md)). -The following list includes all the various kinds of members that may be declared in a class, struct, or record. +The following list includes all the various kinds of members that can be declared in a class, struct, or record. - Fields - Constants @@ -56,7 +56,7 @@ The default accessibility is `private`. Classes (but not structs) support the concept of inheritance. A class that derives from another class, called the *base class*, automatically contains all the public, protected, and internal members of the base class except its constructors and finalizers. -Classes may be declared as [abstract](../../language-reference/keywords/abstract.md), which means that one or more of their methods have no implementation. Although abstract classes cannot be instantiated directly, they can serve as base classes for other classes that provide the missing implementation. Classes can also be declared as [sealed](../../language-reference/keywords/sealed.md) to prevent other classes from inheriting from them. +Classes can be declared as [abstract](../../language-reference/keywords/abstract.md), which means that one or more of their methods have no implementation. Although abstract classes can't be instantiated directly, they can serve as base classes for other classes that provide the missing implementation. Classes can also be declared as [sealed](../../language-reference/keywords/sealed.md) to prevent other classes from inheriting from them. For more information, see [Inheritance](./inheritance.md) and [Polymorphism](./polymorphism.md). @@ -66,7 +66,7 @@ Classes, structs, and records can implement multiple interfaces. To implement fr ## Generic Types -Classes, structs, and records can be defined with one or more type parameters. Client code supplies the type when it creates an instance of the type. For example, the class in the namespace is defined with one type parameter. Client code creates an instance of a `List` or `List` to specify the type that the list will hold. For more information, see [Generics](../types/generics.md). +Classes, structs, and records can be defined with one or more type parameters. Client code supplies the type when it creates an instance of the type. For example, the class in the namespace is defined with one type parameter. Client code creates an instance of a `List` or `List` to specify the type that the list holds. For more information, see [Generics](../types/generics.md). ## Static Types @@ -86,9 +86,9 @@ You can instantiate and initialize class or struct objects, and collections of o ## Anonymous Types -In situations where it isn't convenient or necessary to create a named class you use anonymous types. Anonymous types are defined by their named data members. For more information, see [Anonymous types](../types/anonymous-types.md). +In situations where it isn't convenient or necessary to create a named class you use anonymous types. Named data members define anonymous types. For more information, see [Anonymous types](../types/anonymous-types.md). -## Extension Methods +## Extension Members You can "extend" a class without creating a derived class by creating a separate type. That type contains methods that can be called as if they belonged to the original type. For more information, see [Extension methods](../../programming-guide/classes-and-structs/extension-methods.md). diff --git a/docs/csharp/language-reference/keywords/base.md b/docs/csharp/language-reference/keywords/base.md index 6bda6ee784019..b6395a0ce636a 100644 --- a/docs/csharp/language-reference/keywords/base.md +++ b/docs/csharp/language-reference/keywords/base.md @@ -1,42 +1,34 @@ --- -title: "base keyword" +title: "The base keyword" description: Learn about the base keyword, which is used to access members of the base class from within a derived class in C#. -ms.date: 07/20/2015 +ms.date: 04/17/2025 f1_keywords: - "base" - "BaseClass.BaseClass" - "base_CSharpKeyword" helpviewer_keywords: - "base keyword [C#]" -ms.assetid: 8b645dbe-1a33-49b8-8716-1c401f9a5ea5 --- -# base (C# Reference) +# The base keyword The `base` keyword is used to access members of the base class from within a derived class. Use it if you want to: -- Call a method on the base class that has been overridden by another method. - +- Call a method on the base class overridden by another method. - Specify which base-class constructor should be called when creating instances of the derived class. -The base class access is permitted only in a constructor, in an instance method, and in an instance property accessor. - -Using the `base` keyword from within a static method will give an error. +The base class access is permitted only in a constructor, in an instance method, and in an instance property accessor. Using the `base` keyword from within a static method produces an error. The base class that is accessed is the base class specified in the class declaration. For example, if you specify `class ClassB : ClassA`, the members of ClassA are accessed from ClassB, regardless of the base class of ClassA. -## Example 1 - -In this example, both the base class `Person` and the derived class `Employee` have a method named `GetInfo`. By using the `base` keyword, it is possible to call the `GetInfo` method of the base class from within the derived class. +In this example, both the base class `Person` and the derived class `Employee` have a method named `GetInfo`. By using the `base` keyword, it's possible to call the `GetInfo` method of the base class from within the derived class. -[!code-csharp[csrefKeywordsAccess#1](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs#1)] - -For additional examples, see [new](new-modifier.md), [virtual](virtual.md), and [override](override.md). - -## Example 2 +:::code language="csharp" source="./snippets/csrefKeywordsAccess.cs" id="snippet1"::: This example shows how to specify the base-class constructor called when creating instances of a derived class. -[!code-csharp[csrefKeywordsAccess#2](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs#2)] +:::code language="csharp" source="./snippets/csrefKeywordsAccess.cs" id="snippet2"::: + +For more examples, see [new](new-modifier.md), [virtual](virtual.md), and [override](override.md). ## C# language specification @@ -45,4 +37,4 @@ This example shows how to specify the base-class constructor called when creatin ## See also - [C# Keywords](./index.md) -- [this](./this.md) +- [The `this` keyword](./this.md) diff --git a/docs/csharp/language-reference/keywords/extension.md b/docs/csharp/language-reference/keywords/extension.md new file mode 100644 index 0000000000000..a743f23b6b990 --- /dev/null +++ b/docs/csharp/language-reference/keywords/extension.md @@ -0,0 +1,68 @@ +--- +title: "Extension member declarations" +description: "Learn the syntax to declare extension members in C#. Extension members enable you to add functionality to types and interfaces in those instances where you don't have the source for the original type. Extensions are often paired with generic interfaces to implement a common set of functionality across all types that implement that interface." +ms.date: 04/17/2025 +f1_keywords: + - "extension_CSharpKeyword" + - "extension" +--- +# Extension declaration (C# Reference) + +Beginning with C# 14, top level, nongeneric `static class` declarations can use `extension` containers to declare *extension members*. Extension members are methods or properties and can appear to be instance or static members. Earlier versions of C# enable *extension methods* by adding `this` as a modifier to the first parameter of a static method declared in a top-level, nongeneric static class. + +The `extension` block specifies the type and receiver for extension members. You can declare methods and properties inside the `extension` declaration. The following example declares a single extension block that defines an instance extension method and an instance property. + +:::code language="csharp" source="./snippets/extensions.cs" id="ExtensionMembers"::: + +The `extension` defines the receiver: `sequence`, which is an `IEnumerable`. The receiver type can be nongeneric, an open generic, or a closed generic type. The name `sequence` is in scope in every instance member declared in that extension. The extension method and property both access `sequence`. + +Any of the extension members can be accessed as though they were members of the receiver type: + +:::code language="csharp" source="./snippets/extensions.cs" id="UseExtensionMethod"::: + +You can declare any number of members in a single container, as long as they share the same receiver. You can declare as many extension blocks in a single class as well. Different extensions don't need to declare the same type or name of receiver. The extension parameter doesn't need to include the parameter name if the only members are static: + +:::code language="csharp" source="./snippets/extensions.cs" id="StaticExtensions"::: + +Static extensions can be called as though they're static members of the receiver type: + +:::code language="csharp" source="./snippets/extensions.cs" id="UseStaticExtensions"::: + +> [!IMPORTANT] +> An extension doesn't introduce a *scope* for member declarations. All members declared in a single class, even if in multiple extensions, must have unique signatures. The generated signature includes the receiver type in its name for static members and the receiver parameter for extension instance members. + +The following example shows an extension method using the `this` modifier: + +:::code language="csharp" source="./snippets/ExtensionMethods.cs" id="ExtensionMethod"::: + +The `Add` method can be called from any other method as though it was a member of the `IEnumerable` interface: + +:::code language="csharp" source="./snippets/ExtensionMethods.cs" id="UseExtensionMethod"::: + +Both forms of extension methods generate the same intermediate language (IL). Callers can't make a distinction between them. In fact, you can convert existing extension methods to the new member syntax without a breaking change. The formats are both binary and source compatible. + +## Generic extension blocks + +Where you specify the type parameters for an extension member declared in an extension block depends on where that type parameter is required: + +- You add the type parameter to the `extension` declaration when the type parameter is used in the receiver. +- You add the type parameter to the member declaration when the type is distinct from any type parameter specified on the receiver. +- You can't specify the same type parameter in both locations. + +The following example shows an extension block for `IEnumerable` where two of the extension members require a second type parameter: + +:::code language="csharp" source="./snippets/extensions.cs" id="GenericExtension"::: + +The members `Append` and `Prepend` specify the *extra* type parameter for the conversion. None of the members repeat the type parameter for the receiver. + +The equivalent extension method declarations demonstrate how those type parameters are encoded: + +:::code language="csharp" source="./snippets/ExtensionMethods.cs" id="GenericExtensionMethods"::: + +## See also + +- [Extensions feature specification](~/_csharplang/proposals/extensions.md) + +## C# language specification + +[!INCLUDE[CSharplangspec](~/includes/csharplangspec-md.md)] diff --git a/docs/csharp/language-reference/keywords/index.md b/docs/csharp/language-reference/keywords/index.md index 8cd1a66953ff2..8fd3b1292cac4 100644 --- a/docs/csharp/language-reference/keywords/index.md +++ b/docs/csharp/language-reference/keywords/index.md @@ -1,7 +1,7 @@ --- description: "C# Keywords: Find the reference material for the predefined keywords and contextual keywords defined in the C# language." title: "C# Keywords and contextual keywords" -ms.date: 08/14/2024 +ms.date: 04/17/2025 f1_keywords: - "cs.keywords" helpviewer_keywords: @@ -123,6 +123,7 @@ A contextual keyword is used to provide a specific meaning in the code, but it i [`descending`](descending.md) [`dynamic`](../builtin-types/reference-types.md) [`equals`](equals.md) + [`extension`](extension.md) :::column-end::: :::column::: [`field`](field.md) @@ -137,9 +138,9 @@ A contextual keyword is used to provide a specific meaning in the code, but it i [`let`](let-clause.md) [`managed` (function pointer calling convention)](../unsafe-code.md#function-pointers) [`nameof`](../operators/nameof.md) - [`nint`](../builtin-types/integral-numeric-types.md) :::column-end::: :::column::: + [`nint`](../builtin-types/integral-numeric-types.md) [`not`](../operators/patterns.md#logical-patterns) [`notnull`](../../programming-guide/generics/constraints-on-type-parameters.md#notnull-constraint) [`nuint`](../builtin-types/integral-numeric-types.md) @@ -151,9 +152,9 @@ A contextual keyword is used to provide a specific meaning in the code, but it i [`record`](../../fundamentals/types/records.md) [`remove`](remove.md) [`required`](required.md) - [`scoped`](../statements/declarations.md#scoped-ref) :::column-end::: :::column::: + [`scoped`](../statements/declarations.md#scoped-ref) [`select`](select-clause.md) [`set`](set.md) [`unmanaged` (function pointer calling convention)](../unsafe-code.md#function-pointers) diff --git a/docs/csharp/language-reference/keywords/snippets/ExtensionMethods.cs b/docs/csharp/language-reference/keywords/snippets/ExtensionMethods.cs new file mode 100644 index 0000000000000..5d3d5f95726d9 --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/ExtensionMethods.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace keywords_ExtensionMethods; + +// +public static class NumericSequenceExtensionMethods +{ + public static IEnumerable AddValue(this IEnumerable sequence, int operand) + { + foreach (var item in sequence) + yield return item + operand; + } +} +// + +public static class ExtensionExamples +{ + public static void BasicExample() + { + // + IEnumerable numbers = Enumerable.Range(1, 10); + numbers = numbers.AddValue(10); + // + } +} + +// +public static class GenericExtensions +{ + public static IEnumerable Spread(this IEnumerable source, int start, int count) + => source.Skip(start).Take(count); + + public static IEnumerable Append(this IEnumerable source, IEnumerable second, Func Converter) + { + foreach (T1 item in source) + { + yield return item; + } + foreach (T2 item in second) + { + yield return Converter(item); + } + } + + public static IEnumerable Prepend(this IEnumerable source, IEnumerable second, Func Converter) + { + foreach (T2 item in second) + { + yield return Converter(item); + } + foreach (T1 item in source) + { + yield return item; + } + } +} +// + + diff --git a/docs/csharp/language-reference/keywords/snippets/Extensions.cs b/docs/csharp/language-reference/keywords/snippets/Extensions.cs new file mode 100644 index 0000000000000..90dd06c611058 --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/Extensions.cs @@ -0,0 +1,112 @@ +namespace keywords; + +// +public static class NumericSequences +{ + extension(IEnumerable sequence) + { + public IEnumerable AddValue(int operand) + { + foreach (var item in sequence) + { + yield return item + operand; + } + } + + public int Median + { + get + { + + var sortedList = sequence.OrderBy(n => n).ToList(); + int count = sortedList.Count; + int middleIndex = count / 2; + + if (count % 2 == 0) + { + // Even number of elements: average the two middle elements + return (sortedList[middleIndex - 1] + sortedList[middleIndex]); + } + else + { + // Odd number of elements: return the middle element + return sortedList[middleIndex]; + } + } + } + + public int this[int index] => sequence.Skip(index).First(); + } +} +// + +public static class NumericStaticExtensions +{ + // + extension(IEnumerable) + { + // Method: + public static IEnumerable Generate(int low, int count, int increment) + { + for (int i = 0; i < count; i++) + yield return low + (i * increment); + } + + // Property: + public static IEnumerable Identity => Enumerable.Empty(); + } + // +} + +// +public static class GenericExtensions +{ + extension(IEnumerable source) + { + public IEnumerable Spread(int start, int count) + => source.Skip(start).Take(count); + + public IEnumerable Append(IEnumerable second, Func Converter) + { + foreach(TReceiver item in source) + { + yield return item; + } + foreach (TArg item in second) + { + yield return Converter(item); + } + } + + public IEnumerable Prepend(IEnumerable second, Func Converter) + { + foreach (TArg item in second) + { + yield return Converter(item); + } + foreach (TReceiver item in source) + { + yield return item; + } + } + } +} +// + +public static class ExtensionExamples +{ + public static void BasicExample() + { + // + IEnumerable numbers = Enumerable.Range(1, 10); + numbers = numbers.AddValue(10); + + var median = numbers.Median; + // + + // + var newSequence = IEnumerable.Generate(5, 10, 2); + var identity = IEnumerable.Identity; + // + } +} diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs b/docs/csharp/language-reference/keywords/snippets/csrefKeywordsAccess.cs similarity index 81% rename from samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs rename to docs/csharp/language-reference/keywords/snippets/csrefKeywordsAccess.cs index 92ccb26abeb34..5419cd271e99b 100644 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs +++ b/docs/csharp/language-reference/keywords/snippets/csrefKeywordsAccess.cs @@ -19,7 +19,7 @@ public virtual void GetInfo() } class Employee : Person { - public string id = "ABC567EFG"; + public readonly string id = "ABC567EFG"; public override void GetInfo() { // Calling the base class GetInfo method: @@ -48,12 +48,10 @@ static void Main() // public class BaseClass { - int num; + private int num; - public BaseClass() - { + public BaseClass() => Console.WriteLine("in BaseClass()"); - } public BaseClass(int i) { @@ -61,23 +59,16 @@ public BaseClass(int i) Console.WriteLine("in BaseClass(int i)"); } - public int GetNum() - { - return num; - } + public int GetNum() => num; } public class DerivedClass : BaseClass { // This constructor will call BaseClass.BaseClass() - public DerivedClass() : base() - { - } + public DerivedClass() : base() { } // This constructor will call BaseClass.BaseClass(int i) - public DerivedClass(int i) : base(i) - { - } + public DerivedClass(int i) : base(i) { } static void Main() { @@ -118,7 +109,6 @@ class Employee { private string name; private string alias; - private decimal salary = 3000.00m; // Constructor: public Employee(string name, string alias) @@ -131,26 +121,23 @@ public Employee(string name, string alias) // Printing method: public void printEmployee() { - Console.WriteLine($"Name: {name}\nAlias: {alias}"); + Console.WriteLine($""" + Name: {name} + Alias: {alias} + """); // Passing the object to the CalcTax method by using this: Console.WriteLine($"Taxes: {Tax.CalcTax(this):C}"); } - public decimal Salary - { - get { return salary; } - } + public decimal Salary { get; } = 3000.00m; } class Tax { - public static decimal CalcTax(Employee E) - { - return 0.08m * E.Salary; - } + public static decimal CalcTax(Employee E)=> 0.08m * E.Salary; } - class MainClass + class Program { static void Main() { @@ -177,8 +164,8 @@ class Test // public int this[int param] { - get { return array[param]; } - set { array[param] = value; } + get => array[param]; + set => array[param] = value; } // } diff --git a/docs/csharp/language-reference/keywords/snippets/keywords.csproj b/docs/csharp/language-reference/keywords/snippets/keywords.csproj index d41fcf6772435..e2d57b35b9e37 100644 --- a/docs/csharp/language-reference/keywords/snippets/keywords.csproj +++ b/docs/csharp/language-reference/keywords/snippets/keywords.csproj @@ -3,7 +3,7 @@ enable Exe - net9.0 + net10.0 enable true Keywords.Program diff --git a/docs/csharp/language-reference/keywords/this.md b/docs/csharp/language-reference/keywords/this.md index f39a05ea10390..87cad355b6d9e 100644 --- a/docs/csharp/language-reference/keywords/this.md +++ b/docs/csharp/language-reference/keywords/this.md @@ -1,26 +1,25 @@ --- -title: "this keyword" -description: this keyword (C# Reference) -ms.date: 07/20/2015 +title: "The this keyword" +description: The `this` keyword clarifies access to the current instance of a type, or declares an indexer on the type. +ms.date: 04/17/2025 f1_keywords: - "this" - "this_CSharpKeyword" helpviewer_keywords: - "this keyword [C#]" -ms.assetid: d4f827fe-4710-410b-89b8-867dad44b8a3 --- -# this (C# Reference) +# The this keyword The `this` keyword refers to the current instance of the class and is also used as a modifier of the first parameter of an extension method. > [!NOTE] -> This article discusses the use of `this` with class instances. For more information about its use in extension methods, see [Extension Methods](../../programming-guide/classes-and-structs/extension-methods.md). +> This article discusses the use of `this` with class instances. For more information about its use in extension methods, see the [`extension`](./extension.md) keyword. The following are common uses of `this`: - To qualify members hidden by similar names, for example: - [!code-csharp[csrefKeywordsAccess#4](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs#4)] + :::code language="csharp" source="./snippets/csrefKeywordsAccess.cs" id="snippet4"::: - To pass an object as a parameter to other methods, for example: @@ -30,15 +29,13 @@ The following are common uses of `this`: - To declare [indexers](../../programming-guide/indexers/index.md), for example: - [!code-csharp[csrefKeywordsAccess#5](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs#5)] + :::code language="csharp" source="./snippets/csrefKeywordsAccess.cs" id="snippet5"::: -Static member functions, because they exist at the class level and not as part of an object, do not have a `this` pointer. It is an error to refer to `this` in a static method. +Static member functions, because they exist at the class level and not as part of an object, don't have a `this` pointer. It's an error to refer to `this` in a static method. -## Example +In this example, the parameters `name`, and `alias` hide fields with the same names. The `this` keyword qualifies those variables as `Employee` class members. The `this` keyword also specifies the object for the method `CalcTax`, which belongs to another class. -In this example, `this` is used to qualify the `Employee` class members, `name` and `alias`, which are hidden by similar names. It is also used to pass an object to the method `CalcTax`, which belongs to another class. - -[!code-csharp[csrefKeywordsAccess#3](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/csrefKeywordsAccess.cs#3)] +:::code language="csharp" source="./snippets/csrefKeywordsAccess.cs" id="snippet3"::: ## C# language specification diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index a445ac26198fc..a04a064bdd8a9 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -173,6 +173,8 @@ items: items: - name: add href: ./keywords/add.md + - name: extension + href: ./keywords/extension.md - name: get href: ./keywords/get.md - name: init diff --git a/docs/csharp/linq/how-to-extend-linq.md b/docs/csharp/linq/how-to-extend-linq.md index d4a550ab7af86..a2334c30434da 100644 --- a/docs/csharp/linq/how-to-extend-linq.md +++ b/docs/csharp/linq/how-to-extend-linq.md @@ -2,11 +2,11 @@ title: "How to: Write your own extensions to LINQ" description: Learn techniques to extend the standard LINQ methods. Query based on runtime state, modify query objects, and extend LINQ capabilities. ms.topic: how-to -ms.date: 04/22/2024 +ms.date: 04/17/2025 --- # How to extend LINQ -All LINQ based methods follow one of two similar patterns. They take an enumerable sequence. They return either a different sequence, or a single value. The consistency of the shape enables you to extend LINQ by writing methods with a similar shape. In fact, the .NET libraries have gained new methods in many .NET releases since LINQ was first introduced. In this article, you see examples of extending LINQ by writing your own methods that follow the same pattern. +All LINQ based methods follow one of two similar patterns. They take an enumerable sequence. They return either a different sequence, or a single value. The consistency of the shape enables you to extend LINQ by writing methods with a similar shape. In fact, the .NET libraries gained new methods in many .NET releases since LINQ was first introduced. In this article, you see examples of extending LINQ by writing your own methods that follow the same pattern. ## Add custom methods for LINQ queries @@ -16,11 +16,15 @@ When you extend the interface, An *aggregate* method computes a single value from a set of values. LINQ provides several aggregate methods, including , , and . You can create your own aggregate method by adding an extension method to the interface. -The following code example shows how to create an extension method called `Median` to compute a median for a sequence of numbers of type `double`. +Beginning in C# 14, you can declare an *extension block* to contain multiple extension members. You declare an extension block with the keyword `extension` followed by the receiver parameter in parentheses. The following code example shows how to create an extension method called `Median` in an extension block. The method computes a median for a sequence of numbers of type `double`. + +:::code language="csharp" source="./snippets/HowToExtend/ExtensionMembers.cs" ID="MedianExtensionMember"::: + +You can also add the `this` modifier to a static method to declare an *extension method*. The following code shows the equivalent `Median` extension method: :::code language="csharp" source="./snippets/HowToExtend/LinqExtensions.cs" ID="LinqExtensionClass"::: -You call this extension method for any enumerable collection in the same way you call other aggregate methods from the interface. +You call either extension method for any enumerable collection in the same way you call other aggregate methods from the interface. The following code example shows how to use the `Median` method for an array of type `double`. @@ -58,57 +62,10 @@ You can call this extension method for any enumerable collection just as you wou :::code language="csharp" source="./snippets/HowToExtend/Program.cs" ID="SequenceUsage"::: -## Group results by contiguous keys - -The following example shows how to group elements into chunks that represent subsequences of contiguous keys. For example, assume that you're given the following sequence of key-value pairs: - -|Key | Value | -|----|--------| -| A | We | -| A | think | -| A | that | -| B | Linq | -| C | is | -| A | really | -| B | cool | -| B | ! | - -The following groups are created in this order: - -1. We, think, that -1. Linq -1. is -1. really -1. cool, ! - -The solution is implemented as a thread-safe extension method that returns its results in a streaming manner. It produces its groups as it moves through the source sequence. Unlike the `group` or `orderby` operators, it can begin returning groups to the caller before reading the entire sequence. The following example shows both the extension method and the client code that uses it: - -:::code language="csharp" source="./snippets/HowToExtend/GroupByContiguousKeys.cs" id="group_by_contiguous_keys_chunkextensions"::: - -:::code language="csharp" source="./snippets/HowToExtend/GroupByContiguousKeys.cs" id="group_by_contiguous_keys_client_code"::: - -### `ChunkExtensions` class - -In the presented code of the `ChunkExtensions` class implementation, the `while(true)` loop in the `ChunkBy` method iterates through source sequence and creates a copy of each Chunk. On each pass, the iterator advances to the first element of the next "Chunk", represented by a [`Chunk`](#chunk-class) object, in the source sequence. This loop corresponds to the outer foreach loop that executes the query. In that loop, the code does the following actions: - -1. Get the key for the current Chunk and assign it to `key` variable. The source iterator consumes the source sequence until it finds an element with a key that doesn't match. -1. Make a new Chunk (group) object, and store it in `current` variable. It has one GroupItem, a copy of the current source element. -1. Return that Chunk. A Chunk is an `IGrouping`, which is the return value of the [`ChunkBy`](#chunk-class) method. The Chunk only has the first element in its source sequence. The remaining elements are returned only when the client code foreach's over this chunk. See `Chunk.GetEnumerator` for more info. -1. Check to see if: - - The chunk has a copy of all its source elements, or - - The iterator reached the end of the source sequence. -1. When the caller has enumerated all the chunk items, the `Chunk.GetEnumerator` method has copied all chunk items. If the `Chunk.GetEnumerator` loop didn't enumerate all elements in the chunk, do it now to avoid corrupting the iterator for clients that might be calling it on a separate thread. - -### `Chunk` class - -The `Chunk` class is a contiguous group of one or more source elements that have the same key. A Chunk has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence: - -:::code language="csharp" source="./snippets/HowToExtend/GroupByContiguousKeys.cs" id="group_by_contiguous_keys_chunk_class"::: - -Each `ChunkItem` (represented by `ChunkItem` class) has a reference to the next `ChunkItem` in the list. The list consists of its `head` - which stores the contents of the first source element that belongs with this chunk, and its `tail` - which is an end of the list. The tail is repositioned each time a new `ChunkItem` is added. The tail of the linked list is set to `null` in the `CopyNextChunkElement` method if the key of the next element doesn't match the current chunk's key, or there are no more elements in the source. +Each example shown in this article has a different *receiver*. That means each method must be declared in a different extension block that specifies the unique receiver. The following code example shows a single static class with three different extension blocks, each of which contains one of the methods defined in this article: -The `CopyNextChunkElement` method of the `Chunk` class adds one `ChunkItem` to the current group of items. It tries to advance the iterator on the source sequence. If the `MoveNext()` method returns `false` the iteration is at the end, and `isLastSourceElement` is set to `true`. +:::code language="csharp" source="./snippets/HowToExtend/ExtensionMembers.cs" ID="ExtensionMemberClass"::: -The `CopyAllChunkElements` method is called after the end of the last chunk was reached. It checks whether there are more elements in the source sequence. If there are, it returns `true` if the enumerator for this chunk was exhausted. In this method, when the private `DoneCopyingChunk` field is checked for `true`, if isLastSourceElement is `false`, it signals to the outer iterator to continue iterating. +The final extension block declares a generic extension block. The type parameter for the receiver is declared on the `extension` itself. -The inner foreach loop invokes the `GetEnumerator` method of the `Chunk` class. This method stays one element ahead of the client requests. It adds the next element of the chunk only after the client requests the previous last element in the list. +The preceding example declares one extension member in each extension block. In most cases, you create multiple extension members for the same receiver. In those cases, you should declare the extensions for those members in a single extension block. diff --git a/docs/csharp/linq/snippets/HowToExtend/ExtensionMembers.cs b/docs/csharp/linq/snippets/HowToExtend/ExtensionMembers.cs new file mode 100644 index 0000000000000..28ef9bc4a5fa0 --- /dev/null +++ b/docs/csharp/linq/snippets/HowToExtend/ExtensionMembers.cs @@ -0,0 +1,61 @@ +namespace NewExtensionMember; + +// +public static class EnumerableExtension +{ + // + extension(IEnumerable? source) + { + public double Median() + { + if (source is null || !source.Any()) + { + throw new InvalidOperationException("Cannot compute median for a null or empty set."); + } + + var sortedList = + source.OrderBy(number => number).ToList(); + + int itemIndex = sortedList.Count / 2; + + if (sortedList.Count % 2 == 0) + { + // Even number of items. + return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2; + } + else + { + // Odd number of items. + return sortedList[itemIndex]; + } + } + } + // + + extension(IEnumerable source) + { + public double Median() => + (from number in source select (double)number).Median(); + } + + extension(IEnumerable source) + { + public double Median(Func selector) => + (from num in source select selector(num)).Median(); + + public IEnumerable AlternateElements() + { + int index = 0; + foreach (T element in source) + { + if (index % 2 == 0) + { + yield return element; + } + + index++; + } + } + } +} +// diff --git a/docs/csharp/linq/snippets/HowToExtend/GroupByContiguousKeys.cs b/docs/csharp/linq/snippets/HowToExtend/GroupByContiguousKeys.cs deleted file mode 100644 index 7abbac29d183b..0000000000000 --- a/docs/csharp/linq/snippets/HowToExtend/GroupByContiguousKeys.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace StandardQueryOperators; - -// -public static class ChunkExtensions -{ - public static IEnumerable> ChunkBy( - this IEnumerable source, - Func keySelector) => - source.ChunkBy(keySelector, EqualityComparer.Default); - - public static IEnumerable> ChunkBy( - this IEnumerable source, - Func keySelector, - IEqualityComparer comparer) - { - // Flag to signal end of source sequence. - const bool noMoreSourceElements = true; - - // Auto-generated iterator for the source array. - IEnumerator? enumerator = source.GetEnumerator(); - - // Move to the first element in the source sequence. - if (!enumerator.MoveNext()) - { - yield break; // source collection is empty - } - - while (true) - { - var key = keySelector(enumerator.Current); - - Chunk current = new(key, enumerator, value => comparer.Equals(key, keySelector(value))); - - yield return current; - - if (current.CopyAllChunkElements() == noMoreSourceElements) - { - yield break; - } - } - } -} -// - -// -class Chunk : IGrouping -{ - // INVARIANT: DoneCopyingChunk == true || - // (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current) - - // A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each ChunkItem - // has a reference to the next ChunkItem in the list. - class ChunkItem - { - public ChunkItem(TSource value) => Value = value; - public readonly TSource Value; - public ChunkItem? Next; - } - - public TKey Key { get; } - - // Stores a reference to the enumerator for the source sequence - private IEnumerator enumerator; - - // A reference to the predicate that is used to compare keys. - private Func predicate; - - // Stores the contents of the first source element that - // belongs with this chunk. - private readonly ChunkItem head; - - // End of the list. It is repositioned each time a new - // ChunkItem is added. - private ChunkItem? tail; - - // Flag to indicate the source iterator has reached the end of the source sequence. - internal bool isLastSourceElement; - - // Private object for thread synchronization - private readonly object m_Lock; - - // REQUIRES: enumerator != null && predicate != null - public Chunk(TKey key, [DisallowNull] IEnumerator enumerator, [DisallowNull] Func predicate) - { - Key = key; - this.enumerator = enumerator; - this.predicate = predicate; - - // A Chunk always contains at least one element. - head = new ChunkItem(enumerator.Current); - - // The end and beginning are the same until the list contains > 1 elements. - tail = head; - - m_Lock = new object(); - } - - // Indicates that all chunk elements have been copied to the list of ChunkItems. - private bool DoneCopyingChunk => tail == null; - - // Adds one ChunkItem to the current group - // REQUIRES: !DoneCopyingChunk && lock(this) - private void CopyNextChunkElement() - { - // Try to advance the iterator on the source sequence. - isLastSourceElement = !enumerator.MoveNext(); - - // If we are (a) at the end of the source, or (b) at the end of the current chunk - // then null out the enumerator and predicate for reuse with the next chunk. - if (isLastSourceElement || !predicate(enumerator.Current)) - { - enumerator = default!; - predicate = default!; - } - else - { - tail!.Next = new ChunkItem(enumerator.Current); - } - - // tail will be null if we are at the end of the chunk elements - // This check is made in DoneCopyingChunk. - tail = tail!.Next; - } - - // Called after the end of the last chunk was reached. - internal bool CopyAllChunkElements() - { - while (true) - { - lock (m_Lock) - { - if (DoneCopyingChunk) - { - return isLastSourceElement; - } - else - { - CopyNextChunkElement(); - } - } - } - } - - // Stays just one step ahead of the client requests. - public IEnumerator GetEnumerator() - { - // Specify the initial element to enumerate. - ChunkItem? current = head; - - // There should always be at least one ChunkItem in a Chunk. - while (current != null) - { - // Yield the current item in the list. - yield return current.Value; - - // Copy the next item from the source sequence, - // if we are at the end of our local list. - lock (m_Lock) - { - if (current == tail) - { - CopyNextChunkElement(); - } - } - - // Move to the next ChunkItem in the list. - current = current.Next; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); -} -// - -// -public static class GroupByContiguousKeys -{ - // The source sequence. - static readonly KeyValuePair[] list = [ - new("A", "We"), - new("A", "think"), - new("A", "that"), - new("B", "LINQ"), - new("C", "is"), - new("A", "really"), - new("B", "cool"), - new("B", "!") - ]; - - // Query variable declared as class member to be available - // on different threads. - static readonly IEnumerable>> query = - list.ChunkBy(p => p.Key); - - public static void GroupByContiguousKeys1() - { - // ChunkBy returns IGrouping objects, therefore a nested - // foreach loop is required to access the elements in each "chunk". - foreach (var item in query) - { - Console.WriteLine($"Group key = {item.Key}"); - foreach (var inner in item) - { - Console.WriteLine($"\t{inner.Value}"); - } - } - } -} -// - -public static class GroupByContiguousKeysExamples -{ - public static void RunAllSnippets() - { - GroupByContiguousKeys.GroupByContiguousKeys1(); - } -} diff --git a/docs/csharp/linq/snippets/HowToExtend/HowToExtend.csproj b/docs/csharp/linq/snippets/HowToExtend/HowToExtend.csproj index 238dbed6f0319..ff1b8f8e8e7cc 100644 --- a/docs/csharp/linq/snippets/HowToExtend/HowToExtend.csproj +++ b/docs/csharp/linq/snippets/HowToExtend/HowToExtend.csproj @@ -2,8 +2,9 @@ Exe - net8.0 + net10.0 enable enable + preview diff --git a/docs/csharp/methods.md b/docs/csharp/methods.md index fd1d213d6f4d9..c080032bf08c6 100644 --- a/docs/csharp/methods.md +++ b/docs/csharp/methods.md @@ -2,7 +2,7 @@ title: Overview of methods description: Overview of methods, method parameters, and method return values ms.subservice: fundamentals -ms.date: 11/22/2024 +ms.date: 04/17/2025 --- # Methods in C\# @@ -10,7 +10,7 @@ ms.date: 11/22/2024 A method is a code block that contains a series of statements. A program causes the statements to be executed by calling the method and specifying any required method arguments. In C#, every executed instruction is performed in the context of a method. > [!NOTE] -> This topic discusses named methods. For information about anonymous functions, see [Lambda expressions](language-reference/operators/lambda-expressions.md). +> This article discusses named methods. For information about anonymous functions, see [Lambda expressions](language-reference/operators/lambda-expressions.md). ## Method signatures @@ -25,13 +25,13 @@ Methods are declared in a `class`, `record`, or `struct` by specifying: These parts together form the method signature. > [!IMPORTANT] -> A return type of a method is not part of the signature of the method for the purposes of method overloading. However, it is part of the signature of the method when determining the compatibility between a delegate and the method that it points to. +> A return type of a method isn't part of the signature of the method for the purposes of method overloading. However, it's part of the signature of the method when determining the compatibility between a delegate and the method that it points to. The following example defines a class named `Motorcycle` that contains five methods: :::code language="csharp" source="snippets/methods/methods40.cs" id="snippet40"::: -The `Motorcycle` class includes an overloaded method, `Drive`. Two methods have the same name, but are differentiated by their parameter types. +The `Motorcycle` class includes an overloaded method, `Drive`. Two methods have the same name, but their parameter lists differentiate them. ## Method invocation @@ -143,7 +143,7 @@ The following example calls the `ExampleMethod` method three times. The first tw The use of optional parameters affects *overload resolution*, or the way the C# compiler determines which overload to invoke for a method call, as follows: -- A method, indexer, or constructor is a candidate for execution if each of its parameters corresponds by name or by position to a single argument, and that argument can be converted to the type of the parameter. +- A member is a candidate for execution if each of its parameters corresponds by name or by position to a single argument. Furthermore, that argument can be converted to the type of the parameter. - If more than one candidate is found, overload resolution rules for preferred conversions are applied to the arguments that are explicitly specified. Omitted arguments for optional parameters are ignored. - If two candidates are judged to be equally good, preference goes to a candidate that doesn't have optional parameters for which arguments were omitted in the call. @@ -157,7 +157,7 @@ For example, these two methods use the `return` keyword to return integers: :::code language="csharp" source="snippets/methods/return44.cs" id="snippet44"::: -The examples above are expression bodied members. Expression bodied members return the value returned by the expression. +The preceding examples are expression bodied members. Expression bodied members return the value returned by the expression. You can also choose to define your methods with a statement body and a `return` statement: @@ -218,9 +218,9 @@ Ordinarily, there are two ways to add a method to an existing type: - Modify the source code for that type. Modifying the source creates a breaking change if you also add any private data fields to support the method. - Define the new method in a derived class. A method can't be added in this way using inheritance for other types, such as structures and enumerations. Nor can it be used to "add" a method to a sealed class. -Extension methods let you "add" a method to an existing type without modifying the type itself or implementing the new method in an inherited type. The extension method also doesn't have to reside in the same assembly as the type it extends. You call an extension method as if it were a defined member of a type. +Extension members let you "add" members to an existing type without modifying the type itself or implementing the new method in an inherited type. The extension member also doesn't have to reside in the same assembly as the type it extends. You call an extension method as if it were a defined member of a type. -For more information, see [Extension Methods](programming-guide/classes-and-structs/extension-methods.md). +For more information, see [Extension members](programming-guide/classes-and-structs/extension-methods.md). ## Async Methods diff --git a/docs/csharp/programming-guide/classes-and-structs/extension-methods.md b/docs/csharp/programming-guide/classes-and-structs/extension-methods.md index d5338ed5e23fe..9b0d992b25127 100644 --- a/docs/csharp/programming-guide/classes-and-structs/extension-methods.md +++ b/docs/csharp/programming-guide/classes-and-structs/extension-methods.md @@ -1,84 +1,85 @@ --- -title: "Extension Methods" -description: Extension methods in C# enable you to add methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. -ms.date: 03/15/2024 +title: "Extension members" +description: Extension members in C# enable you to add methods, properties, or operators to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. +ms.date: 04/15/2025 helpviewer_keywords: - "methods [C#], adding to existing types" - "extension methods [C#]" - "methods [C#], extension" -ms.assetid: 175ce3ff-9bbf-4e64-8421-faeb81a0bb51 --- -# Extension Methods (C# Programming Guide) +# Extension members (C# Programming Guide) -Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type. For client code written in C#, F# and Visual Basic, there's no apparent difference between calling an extension method and the methods defined in a type. +Extension members enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. -The most common extension methods are the LINQ standard query operators that add query functionality to the existing and types. To use the standard query operators, first bring them into scope with a `using System.Linq` directive. Then any type that implements appears to have instance methods such as , , , and so on. You can see these additional methods in IntelliSense statement completion when you type "dot" after an instance of an type such as or . +<< Introduce both types. >> -### OrderBy Example +<< Add Extension member lowering here>>. Extension methods are static methods, but they're called as if they were instance methods on the extended type. For client code written in C#, F# and Visual Basic, there's no apparent difference between calling an extension method and the methods defined in a type. -The following example shows how to call the standard query operator `OrderBy` method on an array of integers. The expression in parentheses is a lambda expression. Many standard query operators take lambda expressions as parameters, but this isn't a requirement for extension methods. For more information, see [Lambda Expressions](../../language-reference/operators/lambda-expressions.md). +The most common extension members are the LINQ standard query operators that add query functionality to the existing and types. To use the standard query operators, first bring them into scope with a `using System.Linq` directive. Then any type that implements appears to have instance methods such as , , , and so on. You can see these extra methods in IntelliSense statement completion when you type "dot" after an instance of an type such as or . -[!code-csharp[csProgGuideExtensionMethods#3](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs#3)] +### OrderBy example + +The following example shows how to call the standard query operator `OrderBy` method on an array of integers. The expression in parentheses is a lambda expression. Many standard query operators take lambda expressions as parameters. For more information, see [Lambda Expressions](../../language-reference/operators/lambda-expressions.md). + +:::code language="csharp" source="./snippets/ExtensionMembers/ExtensionMethods.cs" id="UseOrderBy"::: Extension methods are defined as static methods but are called by using instance method syntax. Their first parameter specifies which type the method operates on. The parameter follows the [this](../../language-reference/keywords/this.md) modifier. Extension methods are only in scope when you explicitly import the namespace into your source code with a `using` directive. -The following example shows an extension method defined for the class. It's defined inside a non-nested, non-generic static class: +### Declare extension members -[!code-csharp[csProgGuideExtensionMethods#4](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs#4)] +Beginning with C# 14, you can declare *extension blocks*. An extension block is a block in a non-nested, nongeneric, static class that contains extension members for a type or an instance of that type. The following code example defines an extension block for the `string` type. The extension block contains one member: a method that counts the words in the string: -The `WordCount` extension method can be brought into scope with this `using` directive: +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="ExtensionBlock"::: -```csharp -using ExtensionMethods; -``` +Before C# 14, you declare an extension method by adding the `this` modifier to the first parameter: -And it can be called from an application by using this syntax: +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="ClassicExtensionMethod"::: -```csharp -string s = "Hello Extension Methods"; -int i = s.WordCount(); -``` +Both forms of extensions must be defined inside a non-nested, nongeneric static class. + +And it can be called from an application by using the syntax for accessing instance members: -You invoke the extension method in your code with instance method syntax. The intermediate language (IL) generated by the compiler translates your code into a call on the static method. The principle of encapsulation isn't really being violated. Extension methods can't access private variables in the type they're extending. +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="CallAsInstanceMethod"::: + +While extension members add new capabilities to an existing type, extension members don't violate the principle of encapsulation. The access declarations for all members of the extended type apply to extension members. Both the `MyExtensions` class and the `WordCount` method are `static`, and it can be accessed like all other `static` members. The `WordCount` method can be invoked like other `static` methods as follows: -```csharp -string s = "Hello Extension Methods"; -int i = MyExtensions.WordCount(s); -``` +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="CallAsStaticMethod"::: -The preceding C# code: +The preceding C# code applies to both the extension block and `this` syntax for extension members. The preceding code: - Declares and assigns a new `string` named `s` with a value of `"Hello Extension Methods"`. - Calls `MyExtensions.WordCount` given argument `s`. For more information, see [How to implement and call a custom extension method](./how-to-implement-and-call-a-custom-extension-method.md). -In general, you'll probably be calling extension methods far more often than implementing your own. Because extension methods are called by using instance method syntax, no special knowledge is required to use them from client code. To enable extension methods for a particular type, just add a `using` directive for the namespace in which the methods are defined. For example, to use the standard query operators, add this `using` directive to your code: +In general, you probably call extension members far more often than you implement them. Because extension members are called as though they're declared as members of the extended class, no special knowledge is required to use them from client code. To enable extension members for a particular type, just add a `using` directive for the namespace in which the methods are defined. For example, to use the standard query operators, add this `using` directive to your code: ```csharp using System.Linq; ``` -(You may also have to add a reference to System.Core.dll.) You'll notice that the standard query operators now appear in IntelliSense as additional methods available for most types. +## Binding extension members at compile time -## Binding Extension Methods at Compile Time +You can use extension members to extend a class or interface, but not to override behavior defined in a class. An extension member with the same name and signature as an interface or class members are never called. At compile time, extension members always have lower priority than instance (or static) members defined in the type itself. In other words, if a type has a method named `Process(int i)`, and you have an extension method with the same signature, the compiler always binds to the member method. When the compiler encounters a member invocation, it first looks for a match in the type's members. If no match is found, it searches for any extension members that are defined for the type. It binds to the first extension member that it finds. The following example demonstrates the rules that the C# compiler follows in determining whether to bind to an instance member on the type, or to an extension member. The static class `Extensions` contains extension members defined for any type that implements `IMyInterface`: -You can use extension methods to extend a class or interface, but not to override them. An extension method with the same name and signature as an interface or class method will never be called. At compile time, extension methods always have lower priority than instance methods defined in the type itself. In other words, if a type has a method named `Process(int i)`, and you have an extension method with the same signature, the compiler will always bind to the instance method. When the compiler encounters a method invocation, it first looks for a match in the type's instance methods. If no match is found, it searches for any extension methods that are defined for the type, and bind to the first extension method that it finds. +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="InterfaceAndExtensions"::: -## Example +The equivalent extensions can be declared using the C# 14 extension member syntax: -The following example demonstrates the rules that the C# compiler follows in determining whether to bind a method call to an instance method on the type, or to an extension method. The static class `Extensions` contains extension methods defined for any type that implements `IMyInterface`. Classes `A`, `B`, and `C` all implement the interface. +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="InterfaceAndExtensions"::: -The `MethodB` extension method is never called because its name and signature exactly match methods already implemented by the classes. +Classes `A`, `B`, and `C` all implement the interface: -When the compiler can't find an instance method with a matching signature, it will bind to a matching extension method if one exists. +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="Classes"::: -[!code-csharp[csProgGuideExtensionMethods#5](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs#5)] +The `MethodB` extension method is never called because its name and signature exactly match methods already implemented by the classes. When the compiler can't find an instance method with a matching signature, it binds to a matching extension method if one exists. -## Common Usage Patterns +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="CallExtensionMethods"::: + +## Common usage patterns ### Collection Functionality @@ -86,46 +87,53 @@ In the past, it was common to create "Collection Classes" that implemented the < ### Layer-Specific Functionality -When using an Onion Architecture or other layered application design, it's common to have a set of Domain Entities or Data Transfer Objects that can be used to communicate across application boundaries. These objects generally contain no functionality, or only minimal functionality that applies to all layers of the application. Extension methods can be used to add functionality that is specific to each application layer without loading the object down with methods not needed or wanted in other layers. +When using an Onion Architecture or other layered application design, it's common to have a set of Domain Entities or Data Transfer Objects that can be used to communicate across application boundaries. These objects generally contain no functionality, or only minimal functionality that applies to all layers of the application. Extension methods can be used to add functionality that is specific to each application layer. -```csharp -public class DomainEntity -{ - public int Id { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } -} - -static class DomainEntityExtensions -{ - static string FullName(this DomainEntity value) - => $"{value.FirstName} {value.LastName}"; -} -``` +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="DomainEntity"::: + +You can declare an equivalent `FullName` property in C# 14 and later using the new extension block syntax: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="DomainEntity"::: ### Extending Predefined Types -Rather than creating new objects when reusable functionality needs to be created, we can often extend an existing type, such as a .NET or CLR type. As an example, if we don't use extension methods, we might create an `Engine` or `Query` class to do the work of executing a query on a SQL Server that may be called from multiple places in our code. However we can instead extend the class using extension methods to perform that query from anywhere we have a connection to a SQL Server. Other examples might be to add common functionality to the class, extend the data processing capabilities of the object, and objects for specific error handling functionality. These types of use-cases are limited only by your imagination and good sense. +Rather than creating new objects when reusable functionality needs to be created, you can often extend an existing type, such as a .NET or CLR type. As an example, if you don't use extension methods, you might create an `Engine` or `Query` class to do the work of executing a query on a SQL Server that might be called from multiple places in our code. However you can instead extend the class using extension methods to perform that query from anywhere you have a connection to a SQL Server. Other examples might be to add common functionality to the class, extend the data processing capabilities of the object, and objects for specific error handling functionality. These types of use-cases are limited only by your imagination and good sense. + +Extending predefined types can be difficult with `struct` types because they're passed by value to methods. That means any changes to the struct are made to a copy of the struct. Those changes aren't visible once the extension method exits. You can add the `ref` modifier to the first argument making it a `ref` extension method. The `ref` keyword can appear before or after the `this` keyword without any semantic differences. Adding the `ref` modifier indicates that the first argument is passed by reference. This technique enables you to write extension methods that change the state of the struct being extended (note that private members aren't accessible). Only value types or generic types constrained to struct (For more information about these rules, see [`struct` constraint](../../language-reference/builtin-types/struct.md#struct-constraint) for more information) are allowed as the first parameter of a `ref` extension method or as the receiver of an extension block. The following example shows how to use a `ref` extension method to directly modify a built-in type without the need to reassign the result or pass it through a function with the `ref` keyword: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="RefExtensions"::: + +The equivalent extension blocks are shown in the following code: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="RefExtensions"::: -Extending predefined types can be difficult with `struct` types because they're passed by value to methods. That means any changes to the struct are made to a copy of the struct. Those changes aren't visible once the extension method exits. You can add the `ref` modifier to the first argument making it a `ref` extension method. The `ref` keyword can appear before or after the `this` keyword without any semantic differences. Adding the `ref` modifier indicates that the first argument is passed by reference. This enables you to write extension methods that change the state of the struct being extended (note that private members aren't accessible). Only value types or generic types constrained to struct (see [`struct` constraint](../../language-reference/builtin-types/struct.md#struct-constraint) for more information) are allowed as the first parameter of a `ref` extension method. The following example shows how to use a `ref` extension method to directly modify a built-in type without the need to reassign the result or pass it through a function with the `ref` keyword: +Different extension blocks are required to distinguish by-value and by-ref parameter modes for the receiver. -:::code language="csharp" source="./snippets/methods/Program.cs" id="Snippet9"::: +You can see the difference applying `ref` to the receiver has in the following example: -This next example demonstrates `ref` extension methods for user-defined struct types: +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="UseRefExtension"::: -:::code language="csharp" source="./snippets/methods/Program.cs" id="Snippet10"::: +You can apply the same technique by adding `ref` extension members to user-defined struct types: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="UserDefinedRef"::: + +The preceding sample can also be created using extension blocks in C# 14: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="UserDefinedRef"::: + +You can access these extension methods as follows: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="TestUserDefinedRef"::: ## General Guidelines -While it's still considered preferable to add functionality by modifying an object's code or deriving a new type whenever it's reasonable and possible to do so, extension methods have become a crucial option for creating reusable functionality throughout the .NET ecosystem. For those occasions when the original source isn't under your control, when a derived object is inappropriate or impossible, or when the functionality shouldn't be exposed beyond its applicable scope, Extension methods are an excellent choice. +It's preferable to add functionality by modifying an object's code or deriving a new type whenever it's reasonable and possible to do so. Extension methods are a crucial option for creating reusable functionality throughout the .NET ecosystem. Extension members are preferable when the original source isn't under your control, when a derived object is inappropriate or impossible, or when the functionality has limited scope. For more information on derived types, see [Inheritance](../../fundamentals/object-oriented/inheritance.md). -When using an extension method to extend a type whose source code you aren't in control of, you run the risk that a change in the implementation of the type will cause your extension method to break. - If you do implement extension methods for a given type, remember the following points: -- An extension method is not called if it has the same signature as a method defined in the type. +- An extension method isn't called if it has the same signature as a method defined in the type. - Extension methods are brought into scope at the namespace level. For example, if you have multiple static classes that contain extension methods in a single namespace named `Extensions`, they'll all be brought into scope by the `using Extensions;` directive. For a class library that you implemented, you shouldn't use extension methods to avoid incrementing the version number of an assembly. If you want to add significant functionality to a library for which you own the source code, follow the .NET guidelines for assembly versioning. For more information, see [Assembly Versioning](../../../standard/assembly/versioning.md). diff --git a/docs/csharp/programming-guide/classes-and-structs/how-to-create-a-new-method-for-an-enumeration.md b/docs/csharp/programming-guide/classes-and-structs/how-to-create-a-new-method-for-an-enumeration.md index fbb3ff48cfa61..05275e0968354 100644 --- a/docs/csharp/programming-guide/classes-and-structs/how-to-create-a-new-method-for-an-enumeration.md +++ b/docs/csharp/programming-guide/classes-and-structs/how-to-create-a-new-method-for-an-enumeration.md @@ -1,26 +1,33 @@ --- title: "How to create a new method for an enumeration" description: Learn how to use extension methods to add functionality to an enum in C#. This example shows an extension method called Passing for an enum called Grades. -ms.date: 07/20/2015 +ms.date: 04/17/2025 helpviewer_keywords: - "enumerations [C#]" - "extension methods [C#], for enums" - "enum extensibility [C#]" ms.topic: how-to -ms.assetid: 100106f9-1e54-462c-8ebe-3892fe23b6eb --- # How to create a new method for an enumeration (C# Programming Guide) -You can use extension methods to add functionality specific to a particular enum type. - -## Example +You can use extension methods to add functionality specific to a particular enum type. In the following example, the `Grades` enumeration represents the possible letter grades that a student might receive in a class. An extension method named `Passing` is added to the `Grades` type so that each instance of that type now "knows" whether it represents a passing grade or not. + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="ExtendEnumType"::: + +You can call the extension method as though it was declared on the `enum` type: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMethods.cs" id="ExampleExtendEnum"::: + +Beginning with C# 14, you can declare *extension members* in an extension block. The new syntax enables you to add *extension properties*. You can also add extension members that appear to be new static methods or properties. You're no longer limited to extensions that appear to be instance methods. The following example shows an extension block that adds an instance extension property for `Passing`, and a static extension property for `MinimumPassingGrade`: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="EnumExtensionMembers"::: + +You call these new extension properties as though they're declared on the extended type: + +:::code language="csharp" source="./snippets/ExtensionMembers/CustomExtensionMembers.cs" id="EnumExtensionMembers"::: + +You can learn more about the new extension members in the article on [extension members](./extension-methods.md) and in the language reference article on the ['extension` keyword](../../language-reference/keywords/extension.md). - In the following example, the `Grades` enumeration represents the possible letter grades that a student may receive in a class. An extension method named `Passing` is added to the `Grades` type so that each instance of that type now "knows" whether it represents a passing grade or not. - - [!code-csharp[csProgGuideExtensionMethods#2](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs#2)] - - Note that the `Extensions` class also contains a static variable that is updated dynamically and that the return value of the extension method reflects the current value of that variable. This demonstrates that, behind the scenes, extension methods are invoked directly on the static class in which they are defined. - ## See also - [Extension Methods](./extension-methods.md) diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMembers.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMembers.cs new file mode 100644 index 0000000000000..1e6baeffd8c9f --- /dev/null +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMembers.cs @@ -0,0 +1,195 @@ +// +namespace CustomExtensionMembers; + +public static class MyExtensions +{ + extension(string str) + { + public int WordCount() => + str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length; + } +} +// + +public interface IMyInterface +{ + void MethodB(); +} + +// +public static class Extension +{ + extension(IMyInterface myInterface) + { + public void MethodA(int i) => + Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)"); + + public void MethodA(string s) => + Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)"); + + // This method is never called in ExtensionMethodsDemo1, because each + // of the three classes A, B, and C implements a method named MethodB + // that has a matching signature. + public void MethodB() => + Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)"); + } +} +// + +public class DomainEntity +{ + public int Id { get; set; } + public required string FirstName { get; set; } + public required string LastName { get; set; } +} + +// +static class DomainEntityExtensions +{ + extension(DomainEntity value) + { + string FullName => $"{value.FirstName} {value.LastName}"; + } +} +// + + +public enum Grades +{ + F = 0, + D = 1, + C = 2, + B = 3, + A = 4 +}; + +// +public static class EnumExtensions +{ + private static Grades minimumPassingGrade = Grades.D; + + extension(Grades grade) + { + public static Grades MinimumPassingGrade + { + get => minimumPassingGrade; + set => minimumPassingGrade = value; + } + + public bool Passing => grade >= minimumPassingGrade; + } +} +// + + +// +public static class IntExtensions +{ + extension(int number) + { + public void Increment() + => number++; + } + + // Take note of the extra ref keyword here + extension(ref int number) + { + public void RefIncrement() + => number++; + } +} +// + +public struct Account +{ + public uint id; + public float balance; + + private int secret; +} + +// +public static class AccountExtensions +{ + extension(ref Account account) + { + // ref keyword can also appear before the this keyword + public void Deposit(float amount) + { + account.balance += amount; + + // The following line results in an error as an extension + // method is not allowed to access private members + // account.secret = 1; // CS0122 + } + } +} +// + +public static class ExtensionMemberUsage +{ + public static void Examples() + { + { + // + string s = "Hello Extension Methods"; + int i = s.WordCount(); + // + } + + { + // + string s = "Hello Extension Methods"; + int i = MyExtensions.WordCount(s); + // + } + // + // + Grades g1 = Grades.D; + Grades g2 = Grades.F; + Console.WriteLine($"First {(g1.Passing ? "is" : "is not")} a passing grade."); + Console.WriteLine($"Second {(g2.Passing ? "is" : "is not")} a passing grade."); + + Grades.MinimumPassingGrade = Grades.C; + Console.WriteLine($"\r\nRaising the bar. Passing grade is now {Grades.MinimumPassingGrade}!\r\n"); + Console.WriteLine($"First {(g1.Passing ? "is" : "is not")} a passing grade."); + Console.WriteLine($"Second {(g2.Passing ? "is" : "is not")} a passing grade."); + /* Output: + First is a passing grade. + Second is not a passing grade. + + Raising the bar! + + First is not a passing grade. + Second is not a passing grade. + */ + // + + // + int x = 1; + + // Takes x by value leading to the extension method + // Increment modifying its own copy, leaving x unchanged + x.Increment(); + Console.WriteLine($"x is now {x}"); // x is now 1 + + // Takes x by reference leading to the extension method + // RefIncrement changing the value of x directly + x.RefIncrement(); + Console.WriteLine($"x is now {x}"); // x is now 2 + // + + // + Account account = new() + { + id = 1, + balance = 100f + }; + + Console.WriteLine($"I have ${account.balance}"); // I have $100 + + account.Deposit(50f); + Console.WriteLine($"I have ${account.balance}"); // I have $150 + // + } +} diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMethods.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMethods.cs new file mode 100644 index 0000000000000..900ff560ce814 --- /dev/null +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/CustomExtensionMethods.cs @@ -0,0 +1,242 @@ +// +namespace CustomExtensionMethods; + +public static class MyExtensions +{ + public static int WordCount(this string str) => + str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length; +} +// + +// +public interface IMyInterface +{ + void MethodB(); +} + +// Define extension methods for IMyInterface. + +// The following extension methods can be accessed by instances of any +// class that implements IMyInterface. +public static class Extension +{ + public static void MethodA(this IMyInterface myInterface, int i) => + Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)"); + + public static void MethodA(this IMyInterface myInterface, string s) => + Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)"); + + // This method is never called in ExtensionMethodsDemo1, because each + // of the three classes A, B, and C implements a method named MethodB + // that has a matching signature. + public static void MethodB(this IMyInterface myInterface) => + Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)"); +} +// + +// +// Define three classes that implement IMyInterface, and then use them to test +// the extension methods. +class A : IMyInterface +{ + public void MethodB() { Console.WriteLine("A.MethodB()"); } +} + +class B : IMyInterface +{ + public void MethodB() { Console.WriteLine("B.MethodB()"); } + public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); } +} + +class C : IMyInterface +{ + public void MethodB() { Console.WriteLine("C.MethodB()"); } + public void MethodA(object obj) + { + Console.WriteLine("C.MethodA(object obj)"); + } +} +// + +// +public class DomainEntity +{ + public int Id { get; set; } + public required string FirstName { get; set; } + public required string LastName { get; set; } +} + +static class DomainEntityExtensions +{ + static string FullName(this DomainEntity value) + => $"{value.FirstName} {value.LastName}"; +} +// + + +// +public enum Grades +{ + F = 0, + D = 1, + C = 2, + B = 3, + A = 4 +}; + +// Define an extension method in a non-nested static class. +public static class Extensions +{ + public static bool Passing(this Grades grade, Grades minPassing = Grades.D) => + grade >= minPassing; +} +// + +// +public static class IntExtensions +{ + public static void Increment(this int number) + => number++; + + // Take note of the extra ref keyword here + public static void RefIncrement(this ref int number) + => number++; +} +// + +// +public struct Account +{ + public uint id; + public float balance; + + private int secret; +} + +public static class AccountExtensions +{ + // ref keyword can also appear before the this keyword + public static void Deposit(ref this Account account, float amount) + { + account.balance += amount; + + // The following line results in an error as an extension + // method is not allowed to access private members + // account.secret = 1; // CS0122 + } +} +// + + +public static class ExtensionMethodUsage +{ + public static void Examples() + { + { + // + string s = "Hello Extension Methods"; + int i = s.WordCount(); + // + } + + { + // + string s = "Hello Extension Methods"; + int i = MyExtensions.WordCount(s); + // + } + // + // Declare an instance of class A, class B, and class C. + A a = new A(); + B b = new B(); + C c = new C(); + + // For a, b, and c, call the following methods: + // -- MethodA with an int argument + // -- MethodA with a string argument + // -- MethodB with no argument. + + // A contains no MethodA, so each call to MethodA resolves to + // the extension method that has a matching signature. + a.MethodA(1); // Extension.MethodA(IMyInterface, int) + a.MethodA("hello"); // Extension.MethodA(IMyInterface, string) + + // A has a method that matches the signature of the following call + // to MethodB. + a.MethodB(); // A.MethodB() + + // B has methods that match the signatures of the following + // method calls. + b.MethodA(1); // B.MethodA(int) + b.MethodB(); // B.MethodB() + + // B has no matching method for the following call, but + // class Extension does. + b.MethodA("hello"); // Extension.MethodA(IMyInterface, string) + + // C contains an instance method that matches each of the following + // method calls. + c.MethodA(1); // C.MethodA(object) + c.MethodA("hello"); // C.MethodA(object) + c.MethodB(); // C.MethodB() + /* Output: + Extension.MethodA(this IMyInterface myInterface, int i) + Extension.MethodA(this IMyInterface myInterface, string s) + A.MethodB() + B.MethodA(int i) + B.MethodB() + Extension.MethodA(this IMyInterface myInterface, string s) + C.MethodA(object obj) + C.MethodA(object obj) + C.MethodB() + */ + // + + // + Grades g1 = Grades.D; + Grades g2 = Grades.F; + Console.WriteLine($"First {(g1.Passing() ? "is" : "is not")} a passing grade."); + Console.WriteLine($"Second {(g2.Passing() ? "is" : "is not")} a passing grade."); + + Console.WriteLine("\r\nRaising the bar!\r\n"); + Console.WriteLine($"First {(g1.Passing(Grades.C) ? "is" : "is not")} a passing grade."); + Console.WriteLine($"Second {(g2.Passing(Grades.C) ? "is" : "is not")} a passing grade."); + /* Output: + First is a passing grade. + Second is not a passing grade. + + Raising the bar! + + First is not a passing grade. + Second is not a passing grade. + */ + // + + // + int x = 1; + + // Takes x by value leading to the extension method + // Increment modifying its own copy, leaving x unchanged + x.Increment(); + Console.WriteLine($"x is now {x}"); // x is now 1 + + // Takes x by reference leading to the extension method + // RefIncrement changing the value of x directly + x.RefIncrement(); + Console.WriteLine($"x is now {x}"); // x is now 2 + // + + // + Account account = new() + { + id = 1, + balance = 100f + }; + + Console.WriteLine($"I have ${account.balance}"); // I have $100 + + account.Deposit(50f); + Console.WriteLine($"I have ${account.balance}"); // I have $150 + // + } +} diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.csproj b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMembers.csproj similarity index 54% rename from samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.csproj rename to docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMembers.csproj index 226a169c25be2..0b8f8f2bda02b 100644 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.csproj +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMembers.csproj @@ -2,11 +2,10 @@ Exe - net8.0 - enable + net10.0 enable - ExtensionMethods - Extension2.EnumExtension.Program + enable + preview diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMethods.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMethods.cs new file mode 100644 index 0000000000000..22c3f12745ed2 --- /dev/null +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/ExtensionMethods.cs @@ -0,0 +1,16 @@ + namespace ExtensionMethods; +public static class ExtensionMethods +{ + public static void Examples() + { + // + int[] numbers = [10, 45, 15, 39, 21, 26]; + IOrderedEnumerable result = numbers.OrderBy(g => g); + foreach (int i in result) + { + Console.Write(i + " "); + } + //Output: 10 15 21 26 39 45 + // + } +} diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/Program.cs new file mode 100644 index 0000000000000..51c376c99d71b --- /dev/null +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/ExtensionMembers/Program.cs @@ -0,0 +1,4 @@ + +ExtensionMethods.ExtensionMethods.Examples(); +CustomExtensionMethods.ExtensionMethodUsage.Examples(); +CustomExtensionMembers.ExtensionMemberUsage.Examples(); diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/methods/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/methods/Program.cs index 6c70bd2eeeb55..31878cc4765da 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/methods/Program.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/methods/Program.cs @@ -126,73 +126,3 @@ static void Test() // } } - -// -public static class IntExtensions -{ - public static void Increment(this int number) - => number++; - - // Take note of the extra ref keyword here - public static void RefIncrement(this ref int number) - => number++; -} - -public static class IntProgram -{ - public static void Test() - { - int x = 1; - - // Takes x by value leading to the extension method - // Increment modifying its own copy, leaving x unchanged - x.Increment(); - Console.WriteLine($"x is now {x}"); // x is now 1 - - // Takes x by reference leading to the extension method - // RefIncrement changing the value of x directly - x.RefIncrement(); - Console.WriteLine($"x is now {x}"); // x is now 2 - } -} -// - -// -public struct Account -{ - public uint id; - public float balance; - - private int secret; -} - -public static class AccountExtensions -{ - // ref keyword can also appear before the this keyword - public static void Deposit(ref this Account account, float amount) - { - account.balance += amount; - - // The following line results in an error as an extension - // method is not allowed to access private members - // account.secret = 1; // CS0122 - } -} - -public static class AccountProgram -{ - public static void Test() - { - Account account = new() - { - id = 1, - balance = 100f - }; - - Console.WriteLine($"I have ${account.balance}"); // I have $100 - - account.Deposit(50f); - Console.WriteLine($"I have ${account.balance}"); // I have $150 - } -} -// diff --git a/docs/csharp/specification/toc.yml b/docs/csharp/specification/toc.yml index ef2b617f584c5..59272a001807a 100644 --- a/docs/csharp/specification/toc.yml +++ b/docs/csharp/specification/toc.yml @@ -219,6 +219,8 @@ items: href: ../../../_csharplang/proposals/csharp-10.0/async-method-builders.md - name: Params collections href: ../../../_csharplang/proposals/csharp-13.0/params-collections.md + - name: Extension members + href: ../../../_csharplang/proposals/extensions.md - name: Structs items: - name: Readonly instance members diff --git a/docs/csharp/tour-of-csharp/tips-for-java-developers.md b/docs/csharp/tour-of-csharp/tips-for-java-developers.md index 9d7cb88d434be..089e13fd9ecf8 100644 --- a/docs/csharp/tour-of-csharp/tips-for-java-developers.md +++ b/docs/csharp/tour-of-csharp/tips-for-java-developers.md @@ -1,7 +1,7 @@ --- title: Tips for Java Developers description: Are you new to C#, but experienced in Java? Here's a roadmap of what's familiar, and new features to learn in C#, and features in Java that aren't in C#. -ms.date: 03/17/2025 +ms.date: 04/17/2025 --- # Roadmap for Java developers learning C\# @@ -20,7 +20,7 @@ You can work productively in C# almost immediately because of the similarities. 1. [***Pattern matching***](../fundamentals/functional/pattern-matching.md): Pattern matching enables concise conditional statements and expressions based on the shape of complex data structures. The [`is` statement](../language-reference/operators/is.md) checks if a variable "is" some pattern. The pattern-based [`switch` expression](../language-reference/operators/switch-expression.md) provides a rich syntax to inspect a variable and make decisions based on its characteristics. 1. [***String interpolation***](../language-reference/tokens/interpolated.md) and [***raw string literals***](../language-reference/builtin-types/reference-types.md#string-literals): String interpolation enables you to insert evaluated expressions in a string, rather than using positional identifiers. Raw string literals provide a way to minimize escape sequences in text. 1. [***Nullable and non-nullable types***](../nullable-references.md): C# supports *nullable value types*, and *nullable reference types* by appending the `?` suffix to a type. For nullable types, the compiler warns you if you don't check for `null` before dereferencing the expression. For non-nullable types, the compiler warns you if you might be assigning a `null` value to that variable. Non-nullable reference types minimize programming errors that throw a . -1. [***Extension methods***](../programming-guide/classes-and-structs/extension-methods.md): In C#, you can create methods that *extend* a class or interface. Extension methods extend the behavior of a type from a library, or all types that implement a given interface. +1. [***Extensions***](../programming-guide/classes-and-structs/extension-methods.md): In C#, you can create members that *extend* a class or interface. Extensions provide new behavior for a type from a library, or all types that implement a given interface. 1. [***LINQ***](../linq/index.md): Language integrated query (LINQ) provides a common syntax to query and transform data, regardless of its storage. 1. [***Local functions***](../programming-guide/classes-and-structs/local-functions.md): In C#, you can nest functions inside methods, or other local functions. Local functions provide yet another layer of encapsulation. diff --git a/docs/csharp/whats-new/csharp-14.md b/docs/csharp/whats-new/csharp-14.md index 4b2509bf9e5f2..17c8ad027efc2 100644 --- a/docs/csharp/whats-new/csharp-14.md +++ b/docs/csharp/whats-new/csharp-14.md @@ -1,13 +1,14 @@ --- title: What's new in C# 14 description: Get an overview of the new features in C# 14. C# 14 ships with .NET 10. -ms.date: 04/04/2025 +ms.date: 04/17/2025 ms.topic: whats-new --- # What's new in C# 14 C# 14 includes the following new features. You can try these features using the latest [Visual Studio 2022](https://visualstudio.microsoft.com/vs/preview/) version or the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet): +- [Extension members](#extension-members) - [Null-conditional assignment](#null-conditional-assignment) - [`nameof` supports unbound generic types](#unbound-generic-types-and-nameof) - [More implicit conversions for `Span` and `ReadOnlySpan`](#implicit-span-conversions) @@ -25,6 +26,10 @@ You can find any breaking changes introduced in C# 14 in our article on [breakin [!INCLUDE [released-version-feedback](./includes/released-feedback.md)] +## Extension members + +You can learn more details by reading the [feature specification](~/_csharplang/proposals/extensions.md) for the new extension members feature. + ## The `field` keyword The token `field` enables you to write a property accessor body without declaring an explicit backing field. The token `field` is replaced with a compiler synthesized backing field. @@ -119,7 +124,7 @@ You can simplify the preceding code using the `?.` operator: customer?.Order = GetCurrentOrder(); ``` -The right side of the `=` operator is evaluated only when the left side is not null. If `customer` is null, the code won't call `GetCurrentOrder`. +The right side of the `=` operator is evaluated only when the left side is not null. If `customer` is null, the code doesn't call `GetCurrentOrder`. In addition to assignment, you can use null conditional member access operators with compound assignment operators (`+=`, `-=`, and others). However, increment and decrement, `++` and `--`, aren't allowed. diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs b/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs deleted file mode 100644 index defc85b661d02..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideExtensionMethods/cs/extensionmethods.cs +++ /dev/null @@ -1,198 +0,0 @@ -namespace Extension2 -{ - // - using System; - - namespace EnumExtension - { - // Define an extension method in a non-nested static class. - public static class Extensions - { - public static bool Passing(this Grades grade, Grades minPassing = Grades.D) - { - return grade >= minPassing; - } - } - - public enum Grades { F = 0, D = 1, C = 2, B = 3, A = 4 }; - class Program - { - static void Main(string[] args) - { - Grades g1 = Grades.D; - Grades g2 = Grades.F; - Console.WriteLine($"First {(g1.Passing() ? "is" : "is not")} a passing grade."); - Console.WriteLine($"Second {(g2.Passing() ? "is" : "is not")} a passing grade."); - - Console.WriteLine("\r\nRaising the bar!\r\n"); - Console.WriteLine($"First {(g1.Passing(Grades.C) ? "is" : "is not")} a passing grade."); - Console.WriteLine($"Second {(g2.Passing(Grades.C) ? "is" : "is not")} a passing grade."); - } - /* Output: - First is a passing grade. - Second is not a passing grade. - - Raising the bar! - - First is not a passing grade. - Second is not a passing grade. - */ - } - } - // -} //namespace Extension2 - -// -class ExtensionMethods2 -{ - static void Main() - { - int[] ints = [10, 45, 15, 39, 21, 26]; - IOrderedEnumerable result = ints.OrderBy(g => g); - foreach (int i in result) - { - Console.Write(i + " "); - } - } -} -//Output: 10 15 21 26 39 45 -// - -// -namespace ExtensionMethods -{ - public static class MyExtensions - { - public static int WordCount(this string str) - { - return str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length; - } - } -} -// - -// -// Define an interface named IMyInterface. -namespace DefineIMyInterface -{ - public interface IMyInterface - { - // Any class that implements IMyInterface must define a method - // that matches the following signature. - void MethodB(); - } -} - -// Define extension methods for IMyInterface. -namespace Extensions -{ - using System; - using DefineIMyInterface; - - // The following extension methods can be accessed by instances of any - // class that implements IMyInterface. - public static class Extension - { - public static void MethodA(this IMyInterface myInterface, int i) - { - Console.WriteLine - ("Extension.MethodA(this IMyInterface myInterface, int i)"); - } - - public static void MethodA(this IMyInterface myInterface, string s) - { - Console.WriteLine - ("Extension.MethodA(this IMyInterface myInterface, string s)"); - } - - // This method is never called in ExtensionMethodsDemo1, because each - // of the three classes A, B, and C implements a method named MethodB - // that has a matching signature. - public static void MethodB(this IMyInterface myInterface) - { - Console.WriteLine - ("Extension.MethodB(this IMyInterface myInterface)"); - } - } -} - -// Define three classes that implement IMyInterface, and then use them to test -// the extension methods. -namespace ExtensionMethodsDemo1 -{ - using System; - using Extensions; - using DefineIMyInterface; - - class A : IMyInterface - { - public void MethodB() { Console.WriteLine("A.MethodB()"); } - } - - class B : IMyInterface - { - public void MethodB() { Console.WriteLine("B.MethodB()"); } - public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); } - } - - class C : IMyInterface - { - public void MethodB() { Console.WriteLine("C.MethodB()"); } - public void MethodA(object obj) - { - Console.WriteLine("C.MethodA(object obj)"); - } - } - - class ExtMethodDemo - { - static void Main(string[] args) - { - // Declare an instance of class A, class B, and class C. - A a = new A(); - B b = new B(); - C c = new C(); - - // For a, b, and c, call the following methods: - // -- MethodA with an int argument - // -- MethodA with a string argument - // -- MethodB with no argument. - - // A contains no MethodA, so each call to MethodA resolves to - // the extension method that has a matching signature. - a.MethodA(1); // Extension.MethodA(IMyInterface, int) - a.MethodA("hello"); // Extension.MethodA(IMyInterface, string) - - // A has a method that matches the signature of the following call - // to MethodB. - a.MethodB(); // A.MethodB() - - // B has methods that match the signatures of the following - // method calls. - b.MethodA(1); // B.MethodA(int) - b.MethodB(); // B.MethodB() - - // B has no matching method for the following call, but - // class Extension does. - b.MethodA("hello"); // Extension.MethodA(IMyInterface, string) - - // C contains an instance method that matches each of the following - // method calls. - c.MethodA(1); // C.MethodA(object) - c.MethodA("hello"); // C.MethodA(object) - c.MethodB(); // C.MethodB() - } - } -} -/* Output: - Extension.MethodA(this IMyInterface myInterface, int i) - Extension.MethodA(this IMyInterface myInterface, string s) - A.MethodB() - B.MethodA(int i) - B.MethodB() - Extension.MethodA(this IMyInterface myInterface, string s) - C.MethodA(object obj) - C.MethodA(object obj) - C.MethodB() - */ -// diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/Project.csproj b/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/Project.csproj deleted file mode 100644 index f0609045a05b6..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsAccess/CS/Project.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - Library - net8.0 - -