Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 14 additions & 22 deletions docs/csharp/fundamentals/types/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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":::

Expand All @@ -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

Expand All @@ -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:

Expand Down Expand Up @@ -82,15 +91,7 @@ The .NET class library includes many static classes, such as <xref:System.Math>

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 <xref:System.Collections.Generic.List`1>, <xref:System.Collections.Generic.Dictionary`2>, and <xref:System.Collections.Generic.HashSet`1>, alongside arrays and <xref:System.Span`1>.

*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<T>`, `Span<T>`, 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

Expand All @@ -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)
Expand Down
42 changes: 22 additions & 20 deletions docs/csharp/fundamentals/types/records.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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).
Expand All @@ -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.
Expand All @@ -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)
Expand Down
9 changes: 0 additions & 9 deletions docs/csharp/fundamentals/types/snippets/classes/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@
// db.example.com:5432 (SSL: True)
// </UsingObjectInitializer>

// <CollectionInitializers>
List<string> languages = ["C#", "F#", "Visual Basic"];

// The spread operator (..) composes collections from existing sequences:
List<string> moreLangs = [.. languages, "Python", "TypeScript"];
Console.WriteLine(string.Join(", ", moreLangs));
// C#, F#, Visual Basic, Python, TypeScript
// </CollectionInitializers>

// <Inheritance>
var manager = new Manager("Satya", "Engineering");
Console.WriteLine($"{manager.Name} manages {manager.Department}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public static class Program
public static void Main()
{
// <EqualityTest>
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ public static class Program
{
public static void Main()
{
// <UsingRecord>
var person = new Person("Grace", "Hopper");
Console.WriteLine(person);
// Person { FirstName = Grace, LastName = Hopper }
// </UsingRecord>

// <Deconstruct>
var (first, last) = person;
Console.WriteLine($"{first} {last}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ public static void Main()
// <RecordClassVsStruct>
// 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
Expand Down
28 changes: 17 additions & 11 deletions docs/csharp/fundamentals/types/structs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down
Loading