diff --git a/docs/csharp/fundamentals/types/classes.md b/docs/csharp/fundamentals/types/classes.md index d980f01fc4339..a8bd9f7a98c8a 100644 --- a/docs/csharp/fundamentals/types/classes.md +++ b/docs/csharp/fundamentals/types/classes.md @@ -10,13 +10,22 @@ ai-usage: ai-assisted > [!TIP] > **New to developing software?** Start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. You'll encounter classes once you need to model objects with behavior and state. > -> **Experienced in another language?** C# classes are similar to classes in Java or C++. Skim the [object initializers](#object-initializers) and [collection initializers](#collection-initializers) sections for C#-specific patterns, and see [Records](records.md) for a data-focused alternative. +> **Experienced in another language?** C# classes are similar to classes in Java or C++. Skim the [object initializers](#object-initializers) section for C#-specific patterns, and see [Records](records.md) for a data-focused alternative. A *class* is a reference type that defines a blueprint for objects. When you create a variable of a class type, the variable holds a *reference* to an object on the managed heap. The variable doesn't hold the object data itself. Assigning a class variable to another variable copies the reference, so both variables point to the same object. Classes are the most common way to define custom types in C#. Use them when you need complex behavior, inheritance, or shared identity between references. +## When to use classes + +Use a class when: + +- The type has complex behavior or manages mutable state. +- You need inheritance to create a base class with derived specializations, or to create a derived type that extends an existing class. +- Instances represent a shared identity, not objects that happen to hold equal values. Two references to the same object should stay in sync. +- The type is large or long-lived and benefits from [heap allocation](../../../standard/garbage-collection/fundamentals.md#the-managed-heap) and reference semantics. + ## Declare a class -Define a class with the `class` keyword followed by the type name. An optional [access modifier](../../language-reference/keywords/access-modifiers.md) controls visibility. The default is `internal`: +Define a class with the `class` keyword followed by the type name. An optional [access modifier](../../language-reference/keywords/access-modifiers.md) controls visibility. The default is `internal`. Specify `public` to allow callers from other assemblies to use your types. :::code language="csharp" source="snippets/classes/Program.cs" ID="ClassDeclaration"::: @@ -32,7 +41,7 @@ The variable `customer` holds a reference to the object, not the object itself. :::code language="csharp" source="snippets/classes/Program.cs" ID="ReferenceSemantics"::: -This reference-sharing behavior is one distinction between classes and [structs](structs.md), where assignment copies the data. More importantly, classes support [inheritance](#inheritance). You can build hierarchies where derived types reuse and specialize behavior from a base class. Structs can't participate in inheritance hierarchies. For more on the distinction, see [Value types and reference types](index.md#value-types-and-reference-types). +This reference-sharing behavior is one distinction between classes and [structs](structs.md). With structs, assignment copies the data. More importantly, classes support [inheritance](#inheritance). You can build hierarchies where derived types reuse and specialize behavior from a base class. Structs can't participate in inheritance hierarchies. For more on the distinction, see [Value types and reference types](index.md#value-types-and-reference-types). ## Constructors and initialization @@ -42,7 +51,7 @@ When you create an instance, you want its fields and properties initialized to u :::code language="csharp" source="snippets/classes/Containers.cs" ID="ContainerFieldInitializer"::: -Field initializers define *internal* defaults. They don't give callers any way to choose the initial value. To let consumers of the class supply a value, use one of the following techniques. +Field initializers assign a reasonable default to a field or property. This distinguishes it from the following approaches where callers can provide the initial value. **Constructor parameters** require callers to provide values: @@ -82,15 +91,7 @@ The .NET class library includes many static classes, such as Object initializers work with any accessible property that has a `set` or [`init`](../../language-reference/keywords/init.md) accessor. They combine naturally with `required` properties and with constructors that accept some parameters while letting the caller set others. -## Collection initializers - -A *collection* is a type that holds a group of related values—lists, sets, dictionaries, arrays, and spans are all common examples. The .NET class library provides general-purpose collection types such as , , and , alongside arrays and . - -*Collection expressions* (C# 12+) let you populate a collection inline when you create it using bracket syntax: - -:::code language="csharp" source="snippets/classes/Program.cs" ID="CollectionInitializers"::: - -Collection expressions work with arrays, `List`, `Span`, and any type that supports collection initialization. The spread operator (`..`) adds all elements from its operand into the new collection. The operand doesn't have to be a full collection—it can be any expression that produces a sequence, such as a sub-range, a LINQ query, or a filtered subset. For more information, see [Collection expressions (C# reference)](../../language-reference/operators/collection-expressions.md). +When the property is a *collection*, you can use a [Collection expressions (C# reference)](../../language-reference/operators/collection-expressions.md) to initialize that object. ## Inheritance @@ -100,15 +101,6 @@ Classes support *inheritance*. You can define a new class that reuses, extends, A class can inherit from one base class and implement multiple interfaces. Derived classes inherit all members of the base class except constructors. For more information, see [Inheritance](../object-oriented/inheritance.md) and [Interfaces](interfaces.md). -## When to use classes - -Use a class when: - -- The type has complex behavior or manages mutable state. -- You need inheritance to create a base class with derived specializations, or to create a derived type that extends an existing class. -- Instances represent a shared identity, not just a bundle of data (two references to the same object should stay in sync). -- The type is large or long-lived and benefits from heap allocation and reference semantics. - ## See also - [Type system overview](index.md) diff --git a/docs/csharp/fundamentals/types/records.md b/docs/csharp/fundamentals/types/records.md index 6e0c46df72b7b..b2d45434fe1fc 100644 --- a/docs/csharp/fundamentals/types/records.md +++ b/docs/csharp/fundamentals/types/records.md @@ -14,12 +14,30 @@ ai-usage: ai-assisted The [`record`](../../language-reference/builtin-types/record.md) keyword is a modifier you apply to either a `class` or a `struct`. It tells the compiler to generate value equality, a formatted `ToString`, and nondestructive mutation through [`with` expressions](../../language-reference/operators/with-expression.md). The underlying type, a class or a struct, still determines whether instances use reference or value semantics. The `record` modifier adds data-friendly behavior on top of those semantics. Use records when a type's primary role is storing data and two instances with the same values should be considered equal. +## When to use records + +Use a record when all of the following conditions are true: + +- The type's primary role is storing data. +- Two instances with the same values should be equal. +- You want immutability (especially for `record class` types). +- You want a readable `ToString` without writing one manually. + +When choosing between `record class` and `record struct`: + +- Use `record class` when you need inheritance, or when the type is large enough that copying on every assignment would be expensive. +- Use `record struct` for small, self-contained values where copy semantics and stack allocation are beneficial. + +Avoid records for entity types in [Entity Framework Core](/ef/core/), which depends on reference equality to track entities. For a broader comparison of type options, see [Choose which kind of type](index.md#choose-which-kind-of-type). + ## Declare a record You can apply `record` to either a class or a struct. The simplest form uses *positional parameters* that define both the constructor and the properties in a single line: :::code language="csharp" source="snippets/records/FirstRecord.cs" ID="DeclareRecord"::: +The same positional syntax works for `record struct` types: + :::code language="csharp" source="snippets/records/RecordStruct.cs" ID="RecordStructDecl"::: Writing `record` alone is shorthand for [`record class`](../../language-reference/builtin-types/record.md) which is a reference type. Writing [`record struct`](../../language-reference/builtin-types/record.md) creates a value type. The compiler generates properties from the positional parameters in both cases, but the defaults differ: @@ -31,16 +49,14 @@ You can also write records with standard property syntax when you need more cont :::code language="csharp" source="snippets/records/FirstRecord.cs" ID="RecordWithBody"::: -Create and use record instances the same way you create any object: - -:::code language="csharp" source="snippets/records/FirstRecord.cs" ID="UsingRecord"::: - ## `record class` vs. `record struct` Because the `record` modifier preserves the underlying type's semantics, a `record class` and a `record struct` behave differently when you assign or compare references. Assigning a `record class` copies the reference. Both variables point to the same object. Assigning a `record struct` copies the data, so changes to one variable don't affect the other: :::code language="csharp" source="snippets/records/RecordStruct.cs" ID="RecordClassVsStruct"::: +Record structs also provide compiler-generated value equality: + :::code language="csharp" source="snippets/records/RecordStruct.cs" ID="UsingRecordStruct"::: Choose `record class` when you need inheritance or when instances are large enough that copying would be expensive. Choose `record struct` for small, self-contained data where value-type copy semantics are appropriate. For more on value type semantics, see [Structs](structs.md). @@ -66,6 +82,8 @@ Records are often immutable, so you can't change a property after creation. A `w :::code language="csharp" source="snippets/records/ImmutableRecord.cs" ID="WithExpression"::: +The same syntax works for `record struct` types: + :::code language="csharp" source="snippets/records/RecordStruct.cs" ID="RecordStructWith"::: A `with` expression copies the existing instance, then applies the specified property changes. @@ -86,22 +104,6 @@ A `record class` can inherit from another `record class`. A record can't inherit Value equality checks include the run-time type, so a `Person` and a `Student` with the same `FirstName` and `LastName` aren't considered equal. Record structs don't support inheritance because structs can't inherit from other types. -## When to use records - -Use a record when all of the following conditions are true: - -- The type's primary role is storing data. -- Two instances with the same values should be equal. -- You want immutability (especially for `record class` types). -- You want a readable `ToString` without writing one manually. - -When choosing between `record class` and `record struct`: - -- Use `record class` when you need inheritance, or when the type is large enough that copying on every assignment would be expensive. -- Use `record struct` for small, self-contained values where copy semantics and stack allocation are beneficial. - -Avoid records for entity types in [Entity Framework Core](/ef/core/), which depends on reference equality to track entities. For a broader comparison of type options, see [Choose which kind of type](index.md#choose-which-kind-of-type). - ## See also - [Type system overview](index.md) diff --git a/docs/csharp/fundamentals/types/snippets/classes/Program.cs b/docs/csharp/fundamentals/types/snippets/classes/Program.cs index 25c8de0b4bc62..abf538a9587dc 100644 --- a/docs/csharp/fundamentals/types/snippets/classes/Program.cs +++ b/docs/csharp/fundamentals/types/snippets/classes/Program.cs @@ -35,15 +35,6 @@ // db.example.com:5432 (SSL: True) // -// -List languages = ["C#", "F#", "Visual Basic"]; - -// The spread operator (..) composes collections from existing sequences: -List moreLangs = [.. languages, "Python", "TypeScript"]; -Console.WriteLine(string.Join(", ", moreLangs)); -// C#, F#, Visual Basic, Python, TypeScript -// - // var manager = new Manager("Satya", "Engineering"); Console.WriteLine($"{manager.Name} manages {manager.Department}"); diff --git a/docs/csharp/fundamentals/types/snippets/records/EqualityTest.cs b/docs/csharp/fundamentals/types/snippets/records/EqualityTest.cs index 536afc49b9e02..721b1de199ced 100644 --- a/docs/csharp/fundamentals/types/snippets/records/EqualityTest.cs +++ b/docs/csharp/fundamentals/types/snippets/records/EqualityTest.cs @@ -7,6 +7,7 @@ public static class Program public static void Main() { // + // Person is a record type with three properties: FirstName, LastName, and PhoneNumbers. var phones = new string[] { "555-1234" }; var person1 = new Person("Grace", "Hopper", phones); var person2 = new Person("Grace", "Hopper", phones); diff --git a/docs/csharp/fundamentals/types/snippets/records/FirstRecord.cs b/docs/csharp/fundamentals/types/snippets/records/FirstRecord.cs index b3afd1cab985a..93d5050124708 100644 --- a/docs/csharp/fundamentals/types/snippets/records/FirstRecord.cs +++ b/docs/csharp/fundamentals/types/snippets/records/FirstRecord.cs @@ -21,12 +21,7 @@ public static class Program { public static void Main() { - // var person = new Person("Grace", "Hopper"); - Console.WriteLine(person); - // Person { FirstName = Grace, LastName = Hopper } - // - // var (first, last) = person; Console.WriteLine($"{first} {last}"); diff --git a/docs/csharp/fundamentals/types/snippets/records/RecordStruct.cs b/docs/csharp/fundamentals/types/snippets/records/RecordStruct.cs index 4df3de5f01865..81a413fbd26e1 100644 --- a/docs/csharp/fundamentals/types/snippets/records/RecordStruct.cs +++ b/docs/csharp/fundamentals/types/snippets/records/RecordStruct.cs @@ -18,8 +18,7 @@ public static void Main() // // Record class — assignment copies the reference var p1 = new Person("Grace", "Hopper"); - var p2 = p1; - // p1 and p2 point to the same object: + var p2 = p1; // p1 and p2 point to the same object: Console.WriteLine(ReferenceEquals(p1, p2)); // True // Record struct — assignment copies the data diff --git a/docs/csharp/fundamentals/types/structs.md b/docs/csharp/fundamentals/types/structs.md index a2a60b7f19862..eb400387ed607 100644 --- a/docs/csharp/fundamentals/types/structs.md +++ b/docs/csharp/fundamentals/types/structs.md @@ -14,6 +14,17 @@ ai-usage: ai-assisted A *struct* is a value type that holds its data directly in the instance, rather than through a reference to an object on the heap. When you assign a struct to a new variable, the runtime copies the entire instance. Changes to one variable don't affect the other because each variable represents a different instance. Use structs for small, lightweight types whose primary role is storing data rather than modeling behavior. Examples include coordinates, colors, measurements, or configuration settings. +## When to use structs + +Use a struct when your type: + +- Represents a single value or a small group of related values (roughly 16 bytes or less). +- Has value semantics—two instances with the same data should be equal. +- Is primarily a data container rather than a model of behavior. +- Doesn't need inheritance from a base type (structs can't inherit from other structs or classes, but they can implement interfaces). + +For a broader comparison that includes classes, records, tuples, and interfaces, see [Choose which kind of type](index.md#choose-which-kind-of-type). + ## Declare a struct Define a struct with the `struct` keyword. A struct can contain fields, properties, methods, and constructors, just like a class: @@ -44,6 +55,8 @@ The compiler automatically initializes any fields you don't explicitly set in a :::code language="csharp" source="snippets/structs/Program.cs" ID="AutoDefault"::: +The following example displays the default value for `IsBlocked`: + :::code language="csharp" source="snippets/structs/Program.cs" ID="UsingAutoDefault"::: The `IsBlocked` property isn't assigned in the constructor, so the compiler sets it to `false` (the default for `bool`). This feature reduces boilerplate in constructors that only need to set a few fields. @@ -54,27 +67,20 @@ A `readonly struct` guarantees that no instance member modifies the struct's sta :::code language="csharp" source="snippets/structs/Program.cs" ID="ReadonlyStruct"::: +The following example creates a `Temperature` instance and reads its properties: + :::code language="csharp" source="snippets/structs/Program.cs" ID="UsingReadonly"::: When you don't need the entire struct to be immutable, mark individual members as `readonly` instead. A `readonly` member can't modify the struct's state, and the compiler verifies that guarantee: :::code language="csharp" source="snippets/structs/Program.cs" ID="ReadonlyMembers"::: +The following example shows that `readonly` members return updated values when mutable properties change: + :::code language="csharp" source="snippets/structs/Program.cs" ID="UsingReadonlyMembers"::: Marking members `readonly` helps the compiler optimize defensive copies. When you pass a `readonly` struct to a method that accepts an `in` parameter, the compiler knows no copy is needed. -## When to use structs - -Use a struct when your type: - -- Represents a single value or a small group of related values (roughly 16 bytes or less). -- Has value semantics—two instances with the same data should be equal. -- Is primarily a data container rather than a model of behavior. -- Doesn't need inheritance from a base type (structs can't inherit from other structs or classes, but they can implement interfaces). - -For a broader comparison that includes classes, records, tuples, and interfaces, see [Choose which kind of type](index.md#choose-which-kind-of-type). - ## See also - [Type system overview](index.md)