diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json
index 52f0ca6592ae6..ee987079ecb01 100644
--- a/.openpublishing.redirection.csharp.json
+++ b/.openpublishing.redirection.csharp.json
@@ -5304,6 +5304,10 @@
"source_path_from_root": "/docs/csharp/tutorials/pattern-matching.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching"
},
+ {
+ "source_path_from_root": "/docs/csharp/tutorials/records.md",
+ "redirect_url": "/dotnet/csharp/fundamentals/tutorials/records"
+ },
{
"source_path_from_root": "/docs/csharp/tutorials/upgrade-to-nullable-references.md",
"redirect_url": "/dotnet/csharp/nullable-migration-strategies"
diff --git a/docs/architecture/microservices/microservice-ddd-cqrs-patterns/cqrs-microservice-reads.md b/docs/architecture/microservices/microservice-ddd-cqrs-patterns/cqrs-microservice-reads.md
index e84c0a59a8b0f..bb1a809cfe8ea 100644
--- a/docs/architecture/microservices/microservice-ddd-cqrs-patterns/cqrs-microservice-reads.md
+++ b/docs/architecture/microservices/microservice-ddd-cqrs-patterns/cqrs-microservice-reads.md
@@ -192,7 +192,7 @@ The image shows some example values based on the ViewModel types and the possibl
- **ASP.NET Core Web API Help Pages using Swagger**
[https://learn.microsoft.com/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=visual-studio](/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=visual-studio)
-- **Create record types** [https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/records](../../../csharp/tutorials/records.md)
+- **Create record types** [https://learn.microsoft.com/dotnet/csharp/fundamentals/tutorials/records](../../../csharp/fundamentals/tutorials/records.md)
>[!div class="step-by-step"]
>[Previous](eshoponcontainers-cqrs-ddd-microservice.md)
diff --git a/docs/csharp/fundamentals/tutorials/records.md b/docs/csharp/fundamentals/tutorials/records.md
new file mode 100644
index 0000000000000..3f4cb7d4e23ab
--- /dev/null
+++ b/docs/csharp/fundamentals/tutorials/records.md
@@ -0,0 +1,92 @@
+---
+title: Use record types tutorial
+description: Build a small app that models temperature data with records, compares record behavior, and uses with expressions for nondestructive mutation.
+ms.date: 04/14/2026
+ms.topic: tutorial
+ai-usage: ai-assisted
+---
+
+# Use record types
+
+> [!TIP]
+> **New to developing software?** Start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. You get comfortable with classes, methods, and loops there.
+>
+> **Experienced in another language?** This tutorial focuses on C# record features you use every day: value equality, positional syntax, and `with` expressions.
+
+In this tutorial, you build a console app that models daily temperatures by using records and record structs.
+
+In this tutorial, you learn how to:
+
+- Declare positional records and record structs.
+- Build a small record hierarchy.
+- Use compiler-generated equality and formatting.
+- Use `with` expressions for nondestructive mutation.
+
+## Prerequisites
+
+[!INCLUDE [Prerequisites](../../../../includes/prerequisites-basic-winget.md)]
+
+## Create the app and your first record
+
+Create a folder for your app, run `dotnet new console`, and open the generated project.
+
+Add a file named *DailyTemperature.cs*, and add a positional `readonly record struct` for temperature values:
+
+:::code language="csharp" source="./snippets/records/DailyTemperature.cs" ID="TemperatureRecord":::
+
+Add a file named *Program.cs*, and create sample temperature data:
+
+:::code language="csharp" source="./snippets/records/Program.cs" ID="DeclareData":::
+
+This syntax gives you concise data modeling with immutable value semantics.
+
+## Add behavior to the record struct
+
+In *DailyTemperature.cs*, you already added a computed `Mean` property to your record struct:
+
+```csharp
+public double Mean => (HighTemp + LowTemp) / 2.0;
+```
+
+A record struct works well here because each value is small and self-contained.
+
+## Build record types for degree-day calculations
+
+> [!NOTE]
+> **Heating degree-days** and **cooling degree-days** measure how much the daily average temperature deviates from a base temperature (typically 65°F/18°C). Heating degree-days accumulate on cold days when the average is below the base, while cooling degree-days accumulate on warm days when the average is above the base. These calculations help estimate energy consumption for heating or cooling buildings, making them useful for utility companies, building managers, and climate analysis.
+
+Create a file named *DegreeDays.cs* with a hierarchy for heating and cooling degree-day calculations:
+
+:::code language="csharp" source="./snippets/records/InterimSteps.cs" ID="DegreeDaysRecords":::
+
+Now calculate totals from your `Main` method in *Program.cs*:
+
+:::code language="csharp" source="./snippets/records/Program.cs" ID="HeatingAndCooling":::
+
+The generated `ToString` output is useful for quick diagnostics while you iterate.
+
+## Customize output by overriding PrintMembers
+
+When default output includes too much noise, override `PrintMembers` in the base record:
+
+:::code language="csharp" source="./snippets/records/DegreeDays.cs" ID="AddPrintMembers":::
+
+This override keeps output focused on the information you need.
+
+## Use with expressions for nondestructive mutation
+
+Use `with` to create modified copies without mutating the original record:
+
+:::code language="csharp" source="./snippets/records/Program.cs" ID="GrowingDegreeDays":::
+
+You can extend that idea to compute rolling totals from slices of your input data:
+
+:::code language="csharp" source="./snippets/records/Program.cs" ID="RunningFiveDayTotal":::
+
+This approach is useful when you need transformations while preserving original values.
+
+## Next steps
+
+- Review [C# record types](../types/records.md) for deeper guidance.
+- Continue with [Object-oriented C#](oop.md) for broader design patterns.
+- Explore [Converting types](safely-cast-using-pattern-matching-is-and-as-operators.md) to combine records with safe conversion patterns.
diff --git a/docs/csharp/tutorials/snippets/record-types/DailyTemperature.cs b/docs/csharp/fundamentals/tutorials/snippets/records/DailyTemperature.cs
similarity index 100%
rename from docs/csharp/tutorials/snippets/record-types/DailyTemperature.cs
rename to docs/csharp/fundamentals/tutorials/snippets/records/DailyTemperature.cs
diff --git a/docs/csharp/tutorials/snippets/record-types/DegreeDays.cs b/docs/csharp/fundamentals/tutorials/snippets/records/DegreeDays.cs
similarity index 100%
rename from docs/csharp/tutorials/snippets/record-types/DegreeDays.cs
rename to docs/csharp/fundamentals/tutorials/snippets/records/DegreeDays.cs
diff --git a/docs/csharp/tutorials/snippets/record-types/InterimSteps.cs b/docs/csharp/fundamentals/tutorials/snippets/records/InterimSteps.cs
similarity index 100%
rename from docs/csharp/tutorials/snippets/record-types/InterimSteps.cs
rename to docs/csharp/fundamentals/tutorials/snippets/records/InterimSteps.cs
diff --git a/docs/csharp/tutorials/snippets/record-types/Program.cs b/docs/csharp/fundamentals/tutorials/snippets/records/Program.cs
similarity index 100%
rename from docs/csharp/tutorials/snippets/record-types/Program.cs
rename to docs/csharp/fundamentals/tutorials/snippets/records/Program.cs
diff --git a/docs/csharp/tutorials/snippets/record-types/record-types.csproj b/docs/csharp/fundamentals/tutorials/snippets/records/record-types.csproj
similarity index 80%
rename from docs/csharp/tutorials/snippets/record-types/record-types.csproj
rename to docs/csharp/fundamentals/tutorials/snippets/records/record-types.csproj
index bc48d542cde1f..5ecab70f97adf 100644
--- a/docs/csharp/tutorials/snippets/record-types/record-types.csproj
+++ b/docs/csharp/fundamentals/tutorials/snippets/records/record-types.csproj
@@ -3,7 +3,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/docs/csharp/fundamentals/types/conversions.md b/docs/csharp/fundamentals/types/conversions.md
new file mode 100644
index 0000000000000..ce8209114a771
--- /dev/null
+++ b/docs/csharp/fundamentals/types/conversions.md
@@ -0,0 +1,89 @@
+---
+title: "Type conversions, casting, and boxing"
+description: Learn how to convert between C# types by using implicit and explicit conversions, safe casting patterns, boxing and unboxing, and Parse and TryParse APIs.
+ms.date: 04/14/2026
+ms.topic: concept-article
+ai-usage: ai-assisted
+---
+
+# Type conversions, casting, and boxing
+
+> [!TIP]
+> **New to developing software?** Start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. You practice basic types there before you make conversion choices.
+>
+> **Experienced in another language?** C# conversions work like most statically typed languages: widening conversions are implicit, narrowing conversions need explicit casts, and parsing text should favor `TryParse` in user-facing code.
+
+When you write C# code, you often convert values from one type to another. For example, you might convert from `int` to `long`, read text and convert it to a number, or cast a base type to a derived type.
+
+Understanding key terms:
+
+- A *conversion* is the process of changing a value from one type to another.
+- A *cast* is the explicit syntax for conversion, written with parentheses like `(int)value`.
+- An *implicit conversion* is a conversion that happens automatically when the compiler can guarantee it's safe.
+- An *explicit cast* is a conversion you write in code, indicating the conversion might lose information or fail.
+
+Choose the conversion style based on risk:
+
+- Use an implicit conversion when the compiler proves the conversion is safe.
+- Use an explicit cast when data might be lost or the conversion might fail.
+- Use pattern matching or `as` when you need safe reference-type conversions.
+- Use parsing APIs when your source value is text.
+
+## Use implicit and explicit numeric conversions
+
+An *implicit conversion* always succeeds. An *explicit conversion* might fail or lose information.
+
+:::code language="csharp" source="snippets/conversions/Program.cs" ID="ImplicitAndExplicitNumeric":::
+
+An explicit cast tells readers that the conversion might lose information. In the sample, the `double` value is truncated when converted to `int`.
+
+For full conversion tables, see [Built-in numeric conversions](../../language-reference/builtin-types/numeric-conversions.md).
+
+## Convert references safely
+
+Casts on value types typically copy the data to the destination type. Casts on reference types don't copy data; they change how you view the same object. For reference types, you often start with a base type and need to access members from a derived type. Prefer pattern matching so the test and assignment happen together.
+
+:::code language="csharp" source="snippets/conversions/Program.cs" ID="ReferenceConversions":::
+
+Pattern matching keeps the successful cast variable in the smallest practical scope, which improves readability.
+
+If you need a nullable result instead of a conditional branch, use `as`:
+
+:::code language="csharp" source="snippets/conversions/Program.cs" ID="AsOperator":::
+
+Use `as` only with reference types and nullable value types. It returns `null` when conversion fails.
+
+## Understand boxing and unboxing
+
+Boxing converts a value type to `object` or to an implemented interface type. Unboxing extracts the value type from that object reference.
+
+:::code language="csharp" source="snippets/conversions/Program.cs" ID="BoxingAndUnboxing":::
+
+Boxing allocates memory on the managed heap, and unboxing requires a type check. In hot paths, avoid unnecessary boxing because it adds allocations and extra work.
+
+## Parse text by using Parse and TryParse
+
+When you convert user input or file content, start with `TryParse`. It avoids exceptions for expected invalid input and makes failure handling explicit. All parsing APIs create a new object or value type instance from the source string; they don't modify the source.
+
+:::code language="csharp" source="snippets/conversions/Program.cs" ID="ParseAndTryParse":::
+
+Use `Parse` when input is guaranteed to be valid, such as controlled test data. Use `TryParse` for user input, network payloads, and file data.
+
+## Core conversion APIs
+
+Use these APIs most often in everyday code:
+
+-
+-
+-
+-
+-
+
+For advanced conversion behavior and all overloads, review the API reference for the specific target type.
+
+## See also
+
+- [Type system overview](index.md)
+- [Built-in types and literals](built-in-types.md)
+- [Pattern matching](../functional/pattern-matching.md)
+- [How to safely cast by using pattern matching and the is and as operators](../tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md)
diff --git a/docs/csharp/fundamentals/types/delegates-lambdas.md b/docs/csharp/fundamentals/types/delegates-lambdas.md
new file mode 100644
index 0000000000000..e4ff3163643e0
--- /dev/null
+++ b/docs/csharp/fundamentals/types/delegates-lambdas.md
@@ -0,0 +1,75 @@
+---
+title: "Delegates, lambdas, and events"
+description: Learn how to use Func and Action delegates, write lambda expressions, use static lambdas and discard parameters, and understand the basic event subscription model.
+ms.date: 04/14/2026
+ms.topic: concept-article
+ai-usage: ai-assisted
+---
+
+# Delegates, lambdas, and events
+
+> [!TIP]
+> **New to developing software?** Start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. You build core type and method skills there before using delegates.
+>
+> **Experienced in another language?** Think of delegates as strongly typed function variables. In modern C#, you usually write them with lambda expressions and `Func` or `Action` types.
+
+*Delegates* let you pass behavior as data. You use delegates when code needs a callback, a rule, or a transformation that the caller supplies.
+
+In everyday C# code, you most often use:
+
+- `Func` when a delegate returns a value.
+- `Action` when a delegate returns `void`.
+- Lambda expressions to create delegate instances.
+
+A *lambda expression* is an anonymous function with a compact syntax. It lets you write a function inline without naming it, using the arrow operator `=>` to separate parameters from the body.
+
+## Start with Func and Action
+
+ and cover most delegate scenarios without creating a custom delegate type.
+
+:::code language="csharp" source="snippets/delegates-lambdas/Program.cs" ID="FuncAndAction":::
+
+Use descriptive parameter names in lambdas so readers can understand intent without scanning the full method body.
+
+## Pass lambdas to methods
+
+A common pattern is to accept a `Func` or similar delegate parameter and apply it to a sequence of values.
+
+:::code language="csharp" source="snippets/delegates-lambdas/Program.cs" ID="LambdaAsArgument":::
+
+This pattern appears throughout LINQ and many .NET APIs.
+
+## Use static lambdas when capture is unnecessary
+
+A static lambda uses the `static` modifier before the parameter list, for example: `static x => x * 2` or `static (x, y) => x + y`. A static lambda can't capture local variables or instance state from the enclosing scope. *Capturing* means the lambda references variables from the enclosing scope. A *closure* is the combination of the lambda and the captured variables it holds a reference to. A non-static lambda can capture those values, while a static lambda must use only its parameters and values declared inside its body.
+
+:::code language="csharp" source="snippets/delegates-lambdas/Program.cs" ID="StaticLambda":::
+
+Static lambdas make intent clear and prevent accidental captures.
+
+## Use discard parameters when inputs are irrelevant
+
+Sometimes a delegate signature includes parameters you don't need. Use discards to signal that choice clearly.
+
+Common examples include event handlers where you don't use `sender` or `EventArgs`, callbacks where you only need some of several inputs, and LINQ overloads that provide an index you don't use.
+
+:::code language="csharp" source="snippets/delegates-lambdas/Program.cs" ID="DiscardParameters":::
+
+Discards improve readability because they show which parameters matter.
+
+## Event basics: subscribe with a lambda
+
+Events expose notifications. You subscribe with a delegate, often a lambda expression.
+
+Subscribing to an event is optional because publishers can raise events even when no listeners are attached. By contrast, APIs that accept a callback usually require that callback so the API can complete its work.
+
+:::code language="csharp" source="snippets/delegates-lambdas/Program.cs" ID="MinimalEventIntro":::
+
+This model lets publishers raise notifications without knowing subscriber implementation details.
+
+## See also
+
+- [Type system overview](index.md)
+- [Methods](../../methods.md)
+- [Pattern matching](../functional/pattern-matching.md)
+- [Events (C# programming guide)](../../programming-guide/events/index.md)
diff --git a/docs/csharp/fundamentals/types/snippets/conversions/Program.cs b/docs/csharp/fundamentals/types/snippets/conversions/Program.cs
new file mode 100644
index 0000000000000..06b4af6fb22fa
--- /dev/null
+++ b/docs/csharp/fundamentals/types/snippets/conversions/Program.cs
@@ -0,0 +1,101 @@
+namespace ConversionsSample;
+
+public class Animal;
+public class Mammal : Animal
+{
+ public string Name { get; init; } = "Mammal";
+}
+
+public class Reptile : Animal;
+
+public interface ILabelled
+{
+ string Label { get; }
+}
+
+public readonly struct Packet(int id) : ILabelled
+{
+ public int Id { get; } = id;
+ public string Label => $"P-{Id}";
+}
+
+public static class Program
+{
+ public static void Main()
+ {
+ ShowNumericConversions();
+ ShowReferenceConversions();
+ ShowAsOperator();
+ ShowBoxingAndUnboxing();
+ ShowParseAndTryParse();
+ }
+
+ private static void ShowNumericConversions()
+ {
+ //
+ int itemCount = 42;
+ long widened = itemCount; // Implicit conversion.
+
+ double average = 19.75;
+ int truncated = (int)average; // Explicit cast.
+
+ Console.WriteLine($"widened: {widened}, truncated: {truncated}");
+ //
+ }
+
+ private static void ShowReferenceConversions()
+ {
+ //
+ Animal knownAnimal = new Mammal { Name = "River otter" };
+
+ if (knownAnimal is Mammal mammal)
+ {
+ Console.WriteLine($"Pattern match succeeded: {mammal.Name}");
+ }
+
+ Animal unknownAnimal = new Reptile();
+ Console.WriteLine($"Can treat as mammal: {unknownAnimal is Mammal}");
+ //
+ }
+
+ private static void ShowAsOperator()
+ {
+ //
+ object boxedMammal = new Mammal { Name = "Sea lion" };
+ Mammal? maybeMammal = boxedMammal as Mammal;
+ Console.WriteLine(maybeMammal is null ? "Not a mammal" : maybeMammal.Name);
+
+ object boxedReptile = new Reptile();
+ Mammal? noMammal = boxedReptile as Mammal;
+ Console.WriteLine(noMammal is null ? "Safe null result" : noMammal.Name);
+ //
+ }
+
+ private static void ShowBoxingAndUnboxing()
+ {
+ //
+ int temperature = 72;
+ object boxedTemperature = temperature; // Boxing.
+ int unboxedTemperature = (int)boxedTemperature; // Unboxing.
+
+ Packet packet = new(7);
+ ILabelled labelledPacket = packet; // Boxing through an interface reference.
+
+ Console.WriteLine($"Unboxed: {unboxedTemperature}, Label: {labelledPacket.Label}");
+ //
+ }
+
+ private static void ShowParseAndTryParse()
+ {
+ //
+ string textValue = "512";
+ int parsed = int.Parse(textValue);
+
+ string userInput = "12x";
+ bool parsedSuccessfully = int.TryParse(userInput, out int safeValue);
+
+ Console.WriteLine($"parsed: {parsed}");
+ Console.WriteLine(parsedSuccessfully ? $"safe value: {safeValue}" : "Input is not a valid number.");
+ //
+ }
+}
diff --git a/docs/csharp/fundamentals/types/snippets/conversions/conversions.csproj b/docs/csharp/fundamentals/types/snippets/conversions/conversions.csproj
new file mode 100644
index 0000000000000..bad583f080c8c
--- /dev/null
+++ b/docs/csharp/fundamentals/types/snippets/conversions/conversions.csproj
@@ -0,0 +1,8 @@
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
diff --git a/docs/csharp/fundamentals/types/snippets/delegates-lambdas/Program.cs b/docs/csharp/fundamentals/types/snippets/delegates-lambdas/Program.cs
new file mode 100644
index 0000000000000..71ea737c6c0a5
--- /dev/null
+++ b/docs/csharp/fundamentals/types/snippets/delegates-lambdas/Program.cs
@@ -0,0 +1,80 @@
+namespace DelegatesLambdasSample;
+
+public sealed class MessagePublisher
+{
+ public event EventHandler? MessagePublished;
+
+ public void Publish(string message) => MessagePublished?.Invoke(this, message);
+}
+
+public static class Program
+{
+ public static void Main()
+ {
+ ShowFuncAndAction();
+ ShowLambdaAsArgument();
+ ShowStaticLambda();
+ ShowDiscardParameters();
+ ShowMinimalEventIntro();
+ }
+
+ private static void ShowFuncAndAction()
+ {
+ //
+ Func add = (left, right) => left + right;
+ Action report = message => Console.WriteLine($"Report: {message}");
+
+ int total = add(5, 9);
+ report($"5 + 9 = {total}");
+ //
+ }
+
+ private static void ShowLambdaAsArgument()
+ {
+ //
+ int[] numbers = [1, 2, 3, 4, 5, 6];
+ int[] evenNumbers = Filter(numbers, value => value % 2 == 0).ToArray();
+
+ Console.WriteLine(string.Join(", ", evenNumbers));
+ //
+ }
+
+ private static IEnumerable Filter(IEnumerable source, Func predicate)
+ {
+ foreach (int value in source)
+ {
+ if (predicate(value))
+ {
+ yield return value;
+ }
+ }
+ }
+
+ private static void ShowStaticLambda()
+ {
+ //
+ Func isEven = static value => value % 2 == 0;
+
+ Console.WriteLine(isEven(14));
+ Console.WriteLine(isEven(15));
+ //
+ }
+
+ private static void ShowDiscardParameters()
+ {
+ //
+ Action statusUpdate = (_, _, message) => Console.WriteLine(message);
+ statusUpdate(200, 42, "Operation completed");
+ //
+ }
+
+ private static void ShowMinimalEventIntro()
+ {
+ //
+ MessagePublisher publisher = new();
+ publisher.MessagePublished += (_, message) => Console.WriteLine($"Received: {message}");
+
+ publisher.Publish("Records updated");
+ //
+ }
+}
diff --git a/docs/csharp/fundamentals/types/snippets/delegates-lambdas/delegates-lambdas.csproj b/docs/csharp/fundamentals/types/snippets/delegates-lambdas/delegates-lambdas.csproj
new file mode 100644
index 0000000000000..bad583f080c8c
--- /dev/null
+++ b/docs/csharp/fundamentals/types/snippets/delegates-lambdas/delegates-lambdas.csproj
@@ -0,0 +1,8 @@
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml
index db8dec26d82c5..c62c9bd569746 100644
--- a/docs/csharp/toc.yml
+++ b/docs/csharp/toc.yml
@@ -67,6 +67,10 @@ items:
href: fundamentals/types/generics.md
- name: Tuples and deconstruction
href: fundamentals/types/tuples.md
+ - name: Type conversions, casting, and boxing
+ href: fundamentals/types/conversions.md
+ - name: Delegates, lambdas, and events
+ href: fundamentals/types/delegates-lambdas.md
- name: Anonymous types
href: programming-guide/classes-and-structs/anonymous-types.md
# TODO: Delegates, lambdas and events
@@ -145,6 +149,8 @@ items:
href: fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md
- name: Build data-driven algorithms with pattern matching
href: fundamentals/tutorials/pattern-matching.md
+ - name: Use record types
+ href: fundamentals/tutorials/records.md
# separating data and algorithms
# Data transformations
## TODO: Replace with a tutorial (include async exceptions)
@@ -192,8 +198,6 @@ items:
href: whats-new/tutorials/primary-constructors.md
- name: Tutorials
items:
- - name: Explore record types
- href: tutorials/records.md
- name: Explore top-level statements
href: tutorials/top-level-statements.md
- name: Explore indexes and ranges
diff --git a/docs/csharp/tutorials/records.md b/docs/csharp/tutorials/records.md
deleted file mode 100644
index 54986311543e4..0000000000000
--- a/docs/csharp/tutorials/records.md
+++ /dev/null
@@ -1,158 +0,0 @@
----
-title: Use record types tutorial
-description: Learn about how to use record types, build hierarchies of records, and when to choose records over classes.
-ms.date: 11/22/2024
----
-# Create record types
-
-[*Records*](../language-reference/builtin-types/record.md) are types that use *value-based equality*. You can define records as reference types or value types. Two variables of a record type are equal if the record type definitions are identical, and if for every field, the values in both records are equal. Two variables of a class type are equal if the objects referred to are the same class type and the variables refer to the same object. Value-based equality implies other capabilities you probably want in record types. The compiler generates many of those members when you declare a `record` instead of a `class`. The compiler generates those same methods for `record struct` types.
-
-In this tutorial, you learn how to:
-
-> [!div class="checklist"]
->
-> - Decide if you add the `record` modifier to a `class` type.
-> - Declare record types and positional record types.
-> - Substitute your methods for compiler generated methods in records.
-
-## Prerequisites
-
-[!INCLUDE [Prerequisites](../../../includes/prerequisites-basic.md)]
-
-## Characteristics of records
-
-You define a *record* by declaring a type with the `record` keyword, modifying a `class` or `struct` declaration. Optionally, you can omit the `class` keyword to create a `record class`. A record follows value-based equality semantics. To enforce value semantics, the compiler generates several methods for your record type (both for `record class` types and `record struct` types):
-
-- An override of .
-- A virtual `Equals` method whose parameter is the record type.
-- An override of .
-- Methods for `operator ==` and `operator !=`.
-- Record types implement .
-
-Records also provide an override of . The compiler synthesizes methods for displaying records using . You explore those members as you write the code for this tutorial. Records support `with` expressions to enable nondestructive mutation of records.
-
-You can also declare *positional records* using a more concise syntax. The compiler synthesizes more methods for you when you declare positional records:
-
-- A primary constructor whose parameters match the positional parameters on the record declaration.
-- Public properties for each parameter of a primary constructor. These properties are *init-only* for `record class` types and `readonly record struct` types. For `record struct` types, they're *read-write*.
-- A `Deconstruct` method to extract properties from the record.
-
-## Build temperature data
-
-Data and statistics are among the scenarios where you want to use records. For this tutorial, you build an application that computes *degree days* for different uses. *Degree days* are a measure of heat (or lack of heat) over a period of days, weeks, or months. Degree days track and predict energy usage. More hotter days mean more air conditioning, and more colder days mean more furnace usage. Degree days help manage plant populations and correlate to plant growth as the seasons change. Degree days help track animal migrations for species that travel to match climate.
-
-The formula is based on the mean temperature on a given day and a baseline temperature. To compute degree days over time, you'll need the high and low temperature each day for a period of time. Let's start by creating a new application. Make a new console application. Create a new record type in a new file named "DailyTemperature.cs":
-
-:::code language="csharp" source="snippets/record-types/InterimSteps.cs" ID="DailyRecord":::
-
-The preceding code defines a *positional record*. The `DailyTemperature` record is a `readonly record struct`, because you don't intend to inherit from it, and it should be immutable. The `HighTemp` and `LowTemp` properties are *init only properties*, meaning they can be set in the constructor or using a property initializer. If you wanted the positional parameters to be read-write, you declare a `record struct` instead of a `readonly record struct`. The `DailyTemperature` type also has a *primary constructor* that has two parameters that match the two properties. You use the primary constructor to initialize a `DailyTemperature` record. The following code creates and initializes several `DailyTemperature` records. The first uses named parameters to clarify the `HighTemp` and `LowTemp`. The remaining initializers use positional parameters to initialize the `HighTemp` and `LowTemp`:
-
-:::code language="csharp" source="snippets/record-types/Program.cs" ID="DeclareData":::
-
-You can add your own properties or methods to records, including positional records. You need to compute the mean temperature for each day. You can add that property to the `DailyTemperature` record:
-
-:::code language="csharp" source="snippets/record-types/DailyTemperature.cs" ID="TemperatureRecord":::
-
-Let's make sure you can use this data. Add the following code to your `Main` method:
-
-```csharp
-foreach (var item in data)
- Console.WriteLine(item);
-```
-
-Run your application, and you see output that looks similar to the following display (several rows removed for space):
-
-```dotnetcli
-DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }
-DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }
-
-
-DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }
-DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }
-```
-
-The preceding code shows the output from the override of `ToString` synthesized by the compiler. If you prefer different text, you can write your own version of `ToString` that prevents the compiler from synthesizing a version for you.
-
-## Compute degree days
-
-To compute degree days, you take the difference from a baseline temperature and the mean temperature on a given day. To measure heat over time, you discard any days where the mean temperature is below the baseline. To measure cold over time, you discard any days where the mean temperature is above the baseline. For example, the U.S. uses 65 F as the base for both heating and cooling degree days. That's the temperature where no heating or cooling is needed. If a day has a mean temperature of 70 F, that day is five cooling degree days and zero heating degree days. Conversely, if the mean temperature is 55 F, that day is 10 heating degree days and 0 cooling degree days.
-
-You can express these formulas as a small hierarchy of record types: an abstract degree day type and two concrete types for heating degree days and cooling degree days. These types can also be positional records. They take a baseline temperature and a sequence of daily temperature records as arguments to the primary constructor:
-
-:::code language="csharp" source="snippets/record-types/InterimSteps.cs" ID="DegreeDaysRecords":::
-
-The abstract `DegreeDays` record is the shared base class for both the `HeatingDegreeDays` and `CoolingDegreeDays` records. The primary constructor declarations on the derived records show how to manage base record initialization. Your derived record declares parameters for all the parameters in the base record primary constructor. The base record declares and initializes those properties. The derived record doesn't hide them, but only creates and initializes properties for parameters that aren't declared in its base record. In this example, the derived records don't add new primary constructor parameters. Test your code by adding the following code to your `Main` method:
-
-:::code language="csharp" source="snippets/record-types/Program.cs" ID="HeatingAndCooling":::
-
-You get output like the following display:
-
-```dotnetcli
-HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
-CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }
-```
-
-## Define compiler-synthesized methods
-
-Your code calculates the correct number of heating and cooling degree days over that period of time. But this example shows why you might want to replace some of the synthesized methods for records. You can declare your own version of any of the compiler-synthesized methods in a record type except the clone method. The clone method has a compiler-generated name and you can't provide a different implementation. These synthesized methods include a copy constructor, the members of the interface, equality and inequality tests, and . For this purpose, you synthesize `PrintMembers`. You could also declare your own `ToString`, but `PrintMembers` provides a better option for inheritance scenarios. To provide your own version of a synthesized method, the signature must match the synthesized method.
-
-The `TempRecords` element in the console output isn't useful. It displays the type, but nothing else. You can change this behavior by providing your own implementation of the synthesized `PrintMembers` method. The signature depends on modifiers applied to the `record` declaration:
-
-- If a record type is `sealed`, or a `record struct`, the signature is `private bool PrintMembers(StringBuilder builder);`
-- If a record type isn't `sealed` and derives from `object` (that is, it doesn't declare a base record), the signature is `protected virtual bool PrintMembers(StringBuilder builder);`
-- If a record type isn't `sealed` and derives from another record, the signature is `protected override bool PrintMembers(StringBuilder builder);`
-
-These rules are easiest to comprehend through understanding the purpose of `PrintMembers`. `PrintMembers` adds information about each property in a record type to a string. The contract requires base records to add their members to the display and assumes derived members add their members. Each record type synthesizes a `ToString` override that looks similar to the following example for `HeatingDegreeDays`:
-
-```csharp
-public override string ToString()
-{
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.Append("HeatingDegreeDays");
- stringBuilder.Append(" { ");
- if (PrintMembers(stringBuilder))
- {
- stringBuilder.Append(" ");
- }
- stringBuilder.Append("}");
- return stringBuilder.ToString();
-}
-```
-
-You declare a `PrintMembers` method in the `DegreeDays` record that doesn't print the type of the collection:
-
-:::code language="csharp" source="snippets/record-types/DegreeDays.cs" ID="AddPrintMembers":::
-
-The signature declares a `virtual protected` method to match the compiler's version. Don't worry if you get the accessors wrong; the language enforces the correct signature. If you forget the correct modifiers for any synthesized method, the compiler issues warnings or errors that help you get the right signature.
-
-You can declare the `ToString` method as `sealed` in a record type. That prevents derived records from providing a new implementation. Derived records will still contain the `PrintMembers` override. You would seal `ToString` if you didn't want it to display the runtime type of the record. In the preceding example, you'd lose the information on where the record was measuring heating or cooling degree days.
-
-## Nondestructive mutation
-
-The synthesized members in a positional record class don't modify the state of the record. The goal is that you can more easily create immutable records. Remember that you declare a `readonly record struct` to create an immutable record struct. Look again at the preceding declarations for `HeatingDegreeDays` and `CoolingDegreeDays`. The members added perform computations on the values for the record, but don't mutate state. Positional records make it easier for you to create immutable reference types.
-
-Creating immutable reference types means you want to use nondestructive mutation. You create new record instances that are similar to existing record instances using [`with` expressions](../language-reference/operators/with-expression.md). These expressions are a copy construction with extra assignments that modify the copy. The result is a new record instance where each property was copied from the existing record and optionally modified. The original record is unchanged.
-
-Let's add a couple features to your program that demonstrate `with` expressions. First, let's create a new record to compute growing degree days using the same data. *Growing degree days* typically uses 41 F as the baseline and measures temperatures above the baseline. To use the same data, you can create a new record that is similar to the `coolingDegreeDays`, but with a different base temperature:
-
-:::code language="csharp" source="snippets/record-types/Program.cs" ID="GrowingDegreeDays":::
-
-You can compare the number of degrees computed to the numbers generated with a higher baseline temperature. Remember that records are *reference types* and these copies are shallow copies. The array for the data isn't copied, but both records refer to the same data. That fact is an advantage in one other scenario. For growing degree days, it's useful to keep track of the total for the previous five days. You can create new records with different source data using `with` expressions. The following code builds a collection of these accumulations, then displays the values:
-
-:::code language="csharp" source="snippets/record-types/Program.cs" ID="RunningFiveDayTotal":::
-
-You can also use `with` expressions to create copies of records. Don't specify any properties between the braces for the `with` expression. That means create a copy, and don't change any properties:
-
-```csharp
-var growingDegreeDaysCopy = growingDegreeDays with { };
-```
-
-Run the finished application to see the results.
-
-## Summary
-
-This tutorial showed several aspects of records. Records provide concise syntax for types where the fundamental use is storing data. For object-oriented classes, the fundamental use is defining responsibilities. This tutorial focused on *positional records*, where you can use a concise syntax to declare the properties for a record. The compiler synthesizes several members of the record for copying and comparing records. You can add any other members you need for your record types. You can create immutable record types knowing that none of the compiler-generated members would mutate state. And `with` expressions make it easy to support nondestructive mutation.
-
-Records add another way to define types. You use `class` definitions to create object-oriented hierarchies that focus on the responsibilities and behavior of objects. You create `struct` types for data structures that store data and are small enough to copy efficiently. You create `record` types when you want value-based equality and comparison, don't want to copy values, and want to use reference variables. You create `record struct` types when you want the features of records for a type that is small enough to copy efficiently.
-
-You can learn more about records in the [C# language reference article for the record type](../language-reference/builtin-types/record.md) and the [record type specification](~/_csharpstandard/standard/classes.md#1516-synthesized-record-class-members) and [record struct specification](~/_csharplang/proposals/csharp-10.0/record-structs.md).