From 95335bf44c367488a933d77d6ceac83904325562 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 15 Jul 2026 14:36:17 -0400 Subject: [PATCH 1/9] Add Fundamentals collections and LINQ articles (Everyday C# PR 14a) Phase E, PR 14a. Adds two example-heavy Fundamentals concept articles under fundamentals/statements/ with compiling net10.0 snippet projects: - collections.md: arrays, List, Dictionary, adding/ removing/searching elements, collection expressions (C# 12), indexes and ranges (C# 8). - linq.md: query syntax, method syntax, filter/map/reduce/sort/group, lambda expressions, deferred vs eager evaluation. Adds Collections and LINQ entries to the Expressions and statements TOC node. Advanced topics (providers, IQueryable, expression trees, PLINQ, performance, custom collections/operators) are intentionally left to the Language Reference and LINQ sections and linked out. Closes #53556 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f9f7f68-82b8-43d2-a511-1a4f07138403 --- .../fundamentals/statements/collections.md | 76 +++++++++++ docs/csharp/fundamentals/statements/linq.md | 80 ++++++++++++ .../collections-statements/Program.cs | 116 +++++++++++++++++ .../collections-statements.csproj | 9 ++ .../snippets/linq-statements/Program.cs | 119 ++++++++++++++++++ .../linq-statements/linq-statements.csproj | 9 ++ docs/csharp/toc.yml | 4 + 7 files changed, 413 insertions(+) create mode 100644 docs/csharp/fundamentals/statements/collections.md create mode 100644 docs/csharp/fundamentals/statements/linq.md create mode 100644 docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs create mode 100644 docs/csharp/fundamentals/statements/snippets/collections-statements/collections-statements.csproj create mode 100644 docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs create mode 100644 docs/csharp/fundamentals/statements/snippets/linq-statements/linq-statements.csproj diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md new file mode 100644 index 0000000000000..2696e4805c2aa --- /dev/null +++ b/docs/csharp/fundamentals/statements/collections.md @@ -0,0 +1,76 @@ +--- +title: "Collections in C#" +description: Store, update, search, and read groups of values by using arrays, lists, dictionaries, collection expressions, indexes, and ranges. +ms.date: 07/15/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Collections + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete syntax, see [collections](../../language-reference/builtin-types/collections.md), [arrays](../../language-reference/builtin-types/arrays.md), and [collection expressions](../../language-reference/operators/collection-expressions.md) in the language reference. +> +> **Coming from another language?** C# arrays are fixed-size ordered collections, like arrays in Java or C++. is the everyday growable sequence, like Java's `ArrayList`, JavaScript's arrays, or Python's lists. stores values by key, like maps or dictionaries in many languages. + +A *collection* is an object that stores multiple related values. Each value in a collection is an *element*. C# developers commonly start with three collection shapes: arrays for fixed-size ordered data, for ordered data that grows or shrinks, and for values that you find by key. + +## Choose a collection shape + +Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you need a fixed-size sequence. Use when you need a sequence that can add or remove elements. A *map* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from values between square brackets; [later in this article](#create-collections-with-collection-expressions), you learn the syntax in more detail. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ChooseCollection"::: + + and are *generic types*. A generic type uses a type argument, such as `string` or `int`, to say what kind of values it stores. Generics let you reuse the same collection shape, such as a list or dictionary, with different element types, such as `string`, `int`, or a custom type. They also provide *type safety*: the compiler guarantees every element is the declared type, so you don't need casts and can't accidentally store the wrong type. For more information, see [Generic classes and methods](../types/generics.md). + +## Store fixed-size data in arrays + +An array is an ordered collection with a fixed length. You access an array element by its *index*, which is its zero-based position in the array. Index `0` is the first element, index `1` is the second element, and so on. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="Arrays"::: + +Use `foreach` when you want to read every element in order. Use an index when the position matters, such as when you need the first element, the last element, or the position returned by . + +## Grow a sequence with `List` + +A stores elements in order and can grow or shrink as your program runs. Use to append an element, to remove a matching element, to test whether an element exists, and to find an element's position. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListChanges"::: + +A keeps the remaining elements in order when you add or remove items. When you remove an element, later elements move to lower indexes. + +## Look up items by key with `Dictionary` + +A stores key/value pairs. A *key/value pair* is one key and the value associated with that key; .NET represents one pair with . Use the dictionary indexer to add or update a value by key, and use when the key might not exist. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryLookup"::: + +An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. is safer for reads when the key might be missing because it reports both outcomes without throwing an exception. For more information about bracket access, see the [member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-). + +## Create collections with collection expressions + +A *collection expression* creates a collection from values between square brackets. Beginning with C# 12, collection expressions can create arrays, lists, and other collection types. A *spread element* copies the elements from another collection into the new collection. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="CollectionExpressions"::: + +Collection expressions keep initialization concise. A collection expression has no type of its own. Because `[...]` is typeless by itself, the compiler converts the same expression into the collection shape the context calls for. The receiving variable or parameter can turn it into an array, a , or another supported collection type. + +## Read from positions with indexes and ranges + +Beginning with C# 8, an can count from the end of a sequence with `^`, and a can select a slice with `..`. Arrays support both indexes and ranges. supports indexes, including from-end indexes, but it doesn't support the range operator directly. + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="IndexesAndRanges"::: + +Use ranges when the subsection is part of the data you're working with. Use a index when you need one item by position. For more range examples, see [Explore indexes and ranges](../../tutorials/ranges-indexes.md). + +## See also + +- [Iteration statements](iteration.md) +- [LINQ queries](linq.md) +- [Collections](../../language-reference/builtin-types/collections.md) +- [Arrays](../../language-reference/builtin-types/arrays.md) +- [Collection expressions](../../language-reference/operators/collection-expressions.md) +- [Member access operators](../../language-reference/operators/member-access-operators.md) +- [Explore indexes and ranges](../../tutorials/ranges-indexes.md) + + diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md new file mode 100644 index 0000000000000..6e807cd3a1b64 --- /dev/null +++ b/docs/csharp/fundamentals/statements/linq.md @@ -0,0 +1,80 @@ +--- +title: "LINQ queries in C#" +description: Query collections with LINQ by using query syntax, method syntax, common operators, and lambda expressions. +ms.date: 07/15/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# LINQ queries + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For a deeper tour of providers, operators, and advanced scenarios, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. +> +> **Coming from another language?** LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query. + +*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for provider-based queries, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. + +## Query data with LINQ + +The examples in this article read [in-memory collections](collections.md) such as arrays and . A query usually has three parts: get the data source, describe the result, and enumerate the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntax"::: + +The query describes the result before the `foreach` loop reads it. That separation helps you name the data source, the filtering rule, and the result shape clearly. + +## Write the same query with method syntax + +*Query syntax* uses clauses such as `from`, `where`, `orderby`, and `select`. *Method syntax* calls LINQ methods directly. For in-memory sequences, the standard LINQ methods are the built-in methods for filtering, projection, sorting, grouping, and related operations. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="MethodSyntax"::: + +Method syntax is also called *fluent syntax* because each call returns a result that the next call can use. Many queries can use either form. Use the form that makes the query easiest to read. + +## Use lambda expressions in LINQ + +A *lambda expression* is an anonymous function that you can pass as an argument. LINQ method syntax commonly uses lambda expressions to say what each operator should do with each element. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="LambdaExpressions"::: + +In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element stays in the result. For more information, see [Lambda expressions](../../language-reference/operators/lambda-expressions.md). + +## Filter, map, reduce, sort, and group + +LINQ includes operators that match common functional operations. A *filter* keeps only elements that match a condition; in C#, use to filter. A *map* transforms each element into a new value; C# calls this operation a *projection*, and you use for it. A *reduce* combines all elements into a single value, such as a sum or count; in C#, use aggregation methods such as , , or . Use to sort elements. These operators are in the namespace and work with sequences such as . + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="CommonOperators"::: + +A *projection* creates a result value from each input element. In the previous example, the projection creates strings that combine a work item name and its priority. + +### Group related values + +Use when the result should contain groups of elements that share a key. Each group is represented by , which exposes the group key and the elements in that group. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: + +Grouping is useful for summaries, reports, and menus. For joins, nested groupings, and provider-specific behavior, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. + +## Run a query by enumerating it + +Many LINQ operators use *deferred execution*. Deferred execution means the query holds the recipe for producing results until a `foreach` loop, , or runs it. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="DeferredExecution"::: + +If you need a snapshot of the current results, call or and store that result. This call triggers *eager evaluation*, which runs the query immediately and stores the results instead of deferring work until later enumeration. For more detail, see [Introduction to LINQ queries](../../linq/get-started/introduction-to-linq-queries.md). + +## Go deeper with LINQ + +This article uses in-memory collections to teach LINQ syntax and core operators. The LINQ section covers more operators and advanced scenarios, including joins, XML, database providers, dynamic queries, and custom operators. + +## See also + +- [Collections](collections.md) +- [Language Integrated Query (LINQ)](../../linq/index.md) +- [Introduction to LINQ queries](../../linq/get-started/introduction-to-linq-queries.md) +- [Write LINQ queries](../../linq/get-started/write-linq-queries.md) +- [Standard query operators](../../linq/standard-query-operators/index.md) +- [Lambda expressions](../../language-reference/operators/lambda-expressions.md) +- [LINQ and collections](../../linq/how-to-query-collections.md) + + diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs new file mode 100644 index 0000000000000..793b422cda870 --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -0,0 +1,116 @@ +namespace CollectionStatements; + +public static class Program +{ + public static void Main() + { + ChooseCollectionExample(); + ArraysExample(); + ListChangesExample(); + DictionaryLookupExample(); + CollectionExpressionsExample(); + IndexesAndRangesExample(); + } + + private static void ChooseCollectionExample() + { + // + string[] sprintPlan = ["design", "code", "test"]; + List backlog = ["design", "code"]; + Dictionary priorities = new() + { + ["docs"] = 2, + ["tests"] = 1 + }; + + backlog.Add("test"); + + Console.WriteLine($"Array: {string.Join(", ", sprintPlan)}"); // => Array: design, code, test + Console.WriteLine($"List count: {backlog.Count}"); // => List count: 3 + Console.WriteLine($"Priority for docs: {priorities["docs"]}"); // => Priority for docs: 2 + // + } + + private static void ArraysExample() + { + // + string[] stages = ["design", "code", "test", "review"]; + + Console.WriteLine($"First: {stages[0]}"); // => First: design + Console.WriteLine($"test index: {Array.IndexOf(stages, "test")}"); // => test index: 2 + // + } + + private static void ListChangesExample() + { + // + List workItems = ["design", "code", "test"]; + + workItems.Add("review"); + workItems.Remove("code"); + + Console.WriteLine(string.Join(", ", workItems)); // => design, test, review + Console.WriteLine($"Has review: {workItems.Contains("review")}"); // => Has review: True + Console.WriteLine($"Index of test: {workItems.IndexOf("test")}"); // => Index of test: 1 + // + } + + private static void DictionaryLookupExample() + { + // + Dictionary priorities = new() + { + ["docs"] = 2, + ["tests"] = 1 + }; + + priorities["review"] = 3; + priorities.Remove("review"); + + if (priorities.TryGetValue("docs", out int docsPriority)) + { + Console.WriteLine($"docs priority: {docsPriority}"); // => docs priority: 2 + } + else + { + Console.WriteLine("docs missing"); + } + + if (priorities.TryGetValue("deploy", out int deployPriority)) + { + Console.WriteLine($"deploy priority: {deployPriority}"); + } + else + { + Console.WriteLine("deploy missing"); // => deploy missing + } + + Console.WriteLine($"count: {priorities.Count}"); // => count: 2 + // + } + + private static void CollectionExpressionsExample() + { + // + string[] planned = ["design", "code"]; + string[] upcoming = [.. planned, "test"]; + List blocked = ["docs"]; + + Console.WriteLine($"Upcoming: {string.Join(", ", upcoming)}"); // => Upcoming: design, code, test + Console.WriteLine($"Blocked: {string.Join(", ", blocked)}"); // => Blocked: docs + // + } + + private static void IndexesAndRangesExample() + { + // + string[] phases = ["design", "code", "test", "deploy"]; + List checklist = ["design", "test", "review"]; + + Console.WriteLine($"Last: {phases[^1]}"); // => Last: deploy + Console.WriteLine($"Middle: {string.Join(", ", phases[1..3])}"); // => Middle: code, test + Console.WriteLine($"List last: {checklist[^1]}"); // => List last: review + // + } +} + diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/collections-statements.csproj b/docs/csharp/fundamentals/statements/snippets/collections-statements/collections-statements.csproj new file mode 100644 index 0000000000000..843453f90596c --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/collections-statements.csproj @@ -0,0 +1,9 @@ + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs new file mode 100644 index 0000000000000..7c6e5f1cd6b42 --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs @@ -0,0 +1,119 @@ +namespace LinqStatements; + +public static class Program +{ + public static void Main() + { + QuerySyntaxExample(); + MethodSyntaxExample(); + LambdaExpressionsExample(); + CommonOperatorsExample(); + GroupByExample(); + DeferredExecutionExample(); + } + + private static void QuerySyntaxExample() + { + // + string[] names = ["Ana", "Ben", "Cleo", "Dara"]; + + IEnumerable query = + from name in names + where name.Length >= 4 + orderby name + select name; + + foreach (string name in query) + { + Console.WriteLine(name); // => Cleo, then Dara + } + // + } + + private static void MethodSyntaxExample() + { + // + string[] names = ["Ana", "Ben", "Cleo", "Dara"]; + + IEnumerable query = names + .Where(name => name.Length >= 4) + .OrderBy(name => name) + .Select(name => name); + + foreach (string name in query) + { + Console.WriteLine(name); // => Cleo, then Dara + } + // + } + + private static void LambdaExpressionsExample() + { + // + string[] names = ["Ana", "Ben", "Cleo"]; + + IEnumerable shortNames = names + .Where(name => name.Length == 3) + .Select(name => name.ToUpperInvariant()); + + foreach (string name in shortNames) + { + Console.WriteLine(name); // => ANA, then BEN + } + // + } + + private static void CommonOperatorsExample() + { + // + Dictionary priorities = new() + { + ["Docs"] = 2, + ["Code"] = 1, + ["Test"] = 3, + ["Deploy"] = 4 + }; + + IEnumerable plannedWork = priorities + .Where(workItem => workItem.Value <= 3) + .OrderBy(workItem => workItem.Value) + .Select(workItem => $"{workItem.Key}: {workItem.Value}"); + + foreach (string item in plannedWork) + { + Console.WriteLine(item); // => Code: 1, then Docs: 2, then Test: 3 + } + // + } + + private static void GroupByExample() + { + // + string[] labels = ["api", "auth", "docs", "deploy"]; + + IEnumerable> groups = labels.GroupBy(label => label[0]); + + foreach (IGrouping group in groups) + { + Console.WriteLine($"{group.Key}: {string.Join(", ", group)}"); // => a: api, auth, then d: docs, deploy + } + // + } + + private static void DeferredExecutionExample() + { + // + List workItems = ["design", "docs"]; + + IEnumerable query = workItems.Where(item => item.StartsWith('d')); + + workItems.Add("deploy"); + + foreach (string item in query) + { + Console.WriteLine(item); // => design, then docs, then deploy + } + // + } +} + diff --git a/docs/csharp/fundamentals/statements/snippets/linq-statements/linq-statements.csproj b/docs/csharp/fundamentals/statements/snippets/linq-statements/linq-statements.csproj new file mode 100644 index 0000000000000..843453f90596c --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/linq-statements/linq-statements.csproj @@ -0,0 +1,9 @@ + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 4c2d53605975b..511cb5c2df4a0 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -119,6 +119,10 @@ items: href: fundamentals/statements/selection.md - name: Iteration statements href: fundamentals/statements/iteration.md + - name: Collections + href: fundamentals/statements/collections.md + - name: LINQ + href: fundamentals/statements/linq.md - name: Object-oriented programming items: - name: Classes, structs, and records From ac8c6ba8c17cb35d8bc8150a720fc813e9208c54 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 15 Jul 2026 14:57:45 -0400 Subject: [PATCH 2/9] Fix duplicate H1 and blank-line lint in PR 14a articles Rename collections.md H1/title to "Arrays, lists, and dictionaries" to resolve the duplicate-H1 build warning, and collapse multiple-blank-line runs (MD012) in both statements articles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f9f7f68-82b8-43d2-a511-1a4f07138403 --- docs/csharp/fundamentals/statements/collections.md | 6 ++---- docs/csharp/fundamentals/statements/linq.md | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index 2696e4805c2aa..c2a7bf798de1c 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -1,12 +1,12 @@ --- -title: "Collections in C#" +title: "Arrays, lists, and dictionaries in C#" description: Store, update, search, and read groups of values by using arrays, lists, dictionaries, collection expressions, indexes, and ranges. ms.date: 07/15/2026 ms.topic: concept-article ai-usage: ai-assisted --- -# Collections +# Arrays, lists, and dictionaries > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete syntax, see [collections](../../language-reference/builtin-types/collections.md), [arrays](../../language-reference/builtin-types/arrays.md), and [collection expressions](../../language-reference/operators/collection-expressions.md) in the language reference. @@ -72,5 +72,3 @@ Use ranges when the subsection is part of the data you're working with. Use a Date: Wed, 15 Jul 2026 15:34:08 -0400 Subject: [PATCH 3/9] Rename collections article H1/title to Common collection types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f9f7f68-82b8-43d2-a511-1a4f07138403 --- docs/csharp/fundamentals/statements/collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index c2a7bf798de1c..4f70268401a95 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -1,12 +1,12 @@ --- -title: "Arrays, lists, and dictionaries in C#" +title: "Common collection types in C#" description: Store, update, search, and read groups of values by using arrays, lists, dictionaries, collection expressions, indexes, and ranges. ms.date: 07/15/2026 ms.topic: concept-article ai-usage: ai-assisted --- -# Arrays, lists, and dictionaries +# Common collection types > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete syntax, see [collections](../../language-reference/builtin-types/collections.md), [arrays](../../language-reference/builtin-types/arrays.md), and [collection expressions](../../language-reference/operators/collection-expressions.md) in the language reference. From 38ec8ab34a3bf83b51fa1479212f710bac2830ff Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 15 Jul 2026 16:25:49 -0400 Subject: [PATCH 4/9] 2nd pass edits This is looking a little better. --- .../fundamentals/statements/collections.md | 20 ++- docs/csharp/fundamentals/statements/linq.md | 57 ++++-- .../collections-statements/Program.cs | 32 +++- .../snippets/linq-statements/Program.cs | 170 +++++++++++++++++- 4 files changed, 259 insertions(+), 20 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index 4f70268401a95..132fe9aad1613 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -9,11 +9,11 @@ ai-usage: ai-assisted # Common collection types > [!TIP] -> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete syntax, see [collections](../../language-reference/builtin-types/collections.md), [arrays](../../language-reference/builtin-types/arrays.md), and [collection expressions](../../language-reference/operators/collection-expressions.md) in the language reference. +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about collections, see [Collections](../../language-reference/builtin-types/collections.md) in the language reference. For more information about arrays, see [The array reference type](../../language-reference/builtin-types/arrays.md) in the language reference. For more information about collection expressions, see [Collection expressions (Collection literals)](../../language-reference/operators/collection-expressions.md) in the language reference. > > **Coming from another language?** C# arrays are fixed-size ordered collections, like arrays in Java or C++. is the everyday growable sequence, like Java's `ArrayList`, JavaScript's arrays, or Python's lists. stores values by key, like maps or dictionaries in many languages. -A *collection* is an object that stores multiple related values. Each value in a collection is an *element*. C# developers commonly start with three collection shapes: arrays for fixed-size ordered data, for ordered data that grows or shrinks, and for values that you find by key. +A *collection* is an object that stores multiple related values. Each value in a collection is an *element*. Choose an array when the set is fixed, such as the status names your work-item tracker always uses. Choose a when items come and go, such as a backlog that gains new work and drops completed items. Choose a when you look up values by a key, such as finding a work item by ID or a setting by name. The next section turns those examples into a quick decision model. ## Choose a collection shape @@ -21,7 +21,7 @@ Choose the collection that matches how your code uses the data. A *sequence* sto :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ChooseCollection"::: - and are *generic types*. A generic type uses a type argument, such as `string` or `int`, to say what kind of values it stores. Generics let you reuse the same collection shape, such as a list or dictionary, with different element types, such as `string`, `int`, or a custom type. They also provide *type safety*: the compiler guarantees every element is the declared type, so you don't need casts and can't accidentally store the wrong type. For more information, see [Generic classes and methods](../types/generics.md). + and are *generic types*. A generic type uses a type argument, such as `string` or `int`, to say what kind of values it stores. Generics let you reuse the same collection shape, such as a list or dictionary, with different element types, such as `string`, `int`, or a custom type. They also provide *type safety*: the compiler guarantees every element is the declared type, so you don't need casts and can't accidentally store the wrong type. For more information about generic types, see [Generic types and methods](../types/generics.md) in the fundamentals. ## Store fixed-size data in arrays @@ -31,7 +31,7 @@ An array is an ordered collection with a fixed length. You access an array eleme Use `foreach` when you want to read every element in order. Use an index when the position matters, such as when you need the first element, the last element, or the position returned by . -## Grow a sequence with `List` +## Grow and shrink a sequence with `List` A stores elements in order and can grow or shrink as your program runs. Use to append an element, to remove a matching element, to test whether an element exists, and to find an element's position. @@ -39,13 +39,19 @@ A stores elements in order and can grow A keeps the remaining elements in order when you add or remove items. When you remove an element, later elements move to lower indexes. +Use to add one element at a specific position, to add several elements at a position, and to remove an element by index: + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListInsertRemove"::: + +Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. If your code frequently inserts or removes at the front, a might be the wrong collection shape. + ## Look up items by key with `Dictionary` A stores key/value pairs. A *key/value pair* is one key and the value associated with that key; .NET represents one pair with . Use the dictionary indexer to add or update a value by key, and use when the key might not exist. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryLookup"::: -An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. is safer for reads when the key might be missing because it reports both outcomes without throwing an exception. For more information about bracket access, see the [member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-). +An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. is safer for reads when the key might be missing because it reports both outcomes without throwing an exception. For more information about the indexer operator, see [Member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-) in the language reference. ## Create collections with collection expressions @@ -57,11 +63,11 @@ Collection expressions keep initialization concise. A collection expression has ## Read from positions with indexes and ranges -Beginning with C# 8, an can count from the end of a sequence with `^`, and a can select a slice with `..`. Arrays support both indexes and ranges. supports indexes, including from-end indexes, but it doesn't support the range operator directly. +An can count from the end of a sequence with `^`, and a can select a slice with `..`. From-end indexes can appear on either side of `..`. If you omit the start index, the range starts at the beginning. If you omit the end index, the range continues through the last element. The range `..` means the entire collection. Arrays support both indexes and ranges. supports indexes, including from-end indexes, but it doesn't support the range operator directly. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="IndexesAndRanges"::: -Use ranges when the subsection is part of the data you're working with. Use a index when you need one item by position. For more range examples, see [Explore indexes and ranges](../../tutorials/ranges-indexes.md). +Use ranges when the subsection is part of the data you're working with. Use a index when you need one item by position. For more information about indexes and ranges, see the [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) tutorial. ## See also diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index c3aca0d7ca096..195a70672ef25 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -9,19 +9,19 @@ ai-usage: ai-assisted # LINQ queries > [!TIP] -> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For a deeper tour of providers, operators, and advanced scenarios, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about providers, operators, and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md). > > **Coming from another language?** LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query. -*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for provider-based queries, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. +*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md). ## Query data with LINQ -The examples in this article read [in-memory collections](collections.md) such as arrays and . A query usually has three parts: get the data source, describe the result, and enumerate the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. +The examples in this article read [in-memory collections](collections.md) such as arrays and . A query usually has three parts: specify the data source, describe the result, and enumerate the source to produce the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntax"::: -The query describes the result before the `foreach` loop reads it. That separation helps you name the data source, the filtering rule, and the result shape clearly. +The query describes the result before the `foreach` loop reads it. Because a query describes the result before it enumerates the source, you can *compose* it: build up a more complex query from smaller operations, and combine it with other queries, before any work runs. ## Write the same query with method syntax @@ -31,17 +31,32 @@ The query describes the result before the `foreach` loop reads it. That separati Method syntax is also called *fluent syntax* because each call returns a result that the next call can use. Many queries can use either form. Use the form that makes the query easiest to read. +Query syntax often reads well when the query has several clauses. The `let` clause gives a name to an intermediate value before the query filters, sorts, and selects the final result: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxClearer"::: + +Method syntax often reads well for short operations that don't have a query-syntax keyword. For example, returns one value directly: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="MethodSyntaxClearer"::: + ## Use lambda expressions in LINQ A *lambda expression* is an anonymous function that you can pass as an argument. LINQ method syntax commonly uses lambda expressions to say what each operator should do with each element. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="LambdaExpressions"::: -In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element stays in the result. For more information, see [Lambda expressions](../../language-reference/operators/lambda-expressions.md). +In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element stays in the result. For more information about lambda expressions, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. + +Query-syntax clauses use lambda expressions too. Clauses such as `where`, `orderby`, and `select` compile to method calls that take lambda expressions. The range variable becomes the lambda parameter, and the clause expression becomes the lambda body. Query syntax is a concise way to write those same lambdas: -## Filter, map, reduce, sort, and group +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxLambda"::: -LINQ includes operators that match common functional operations. A *filter* keeps only elements that match a condition; in C#, use to filter. A *map* transforms each element into a new value; C# calls this operation a *projection*, and you use for it. A *reduce* combines all elements into a single value, such as a sum or count; in C#, use aggregation methods such as , , or . Use to sort elements. These operators are in the namespace and work with sequences such as . +## Shape data with LINQ methods + +Use to keep only the elements that match a condition. Use to transform each element into a new value; C# calls this operation a *projection*. Use to sort elements. Use aggregation methods such as , , and to combine all elements into a single value. These methods are in the namespace and work with sequences such as . + +> [!NOTE] +> If you know the functional-programming terms, is a *filter*, is a *map* (C# calls it a projection), and aggregation methods such as , , and are a *reduce*. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="CommonOperators"::: @@ -53,15 +68,35 @@ Use when the result should contain groups :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: -Grouping is useful for summaries, reports, and menus. For joins, nested groupings, and provider-specific behavior, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. +Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md). -## Run a query by enumerating it +## Run a query -Many LINQ operators use *deferred execution*. Deferred execution means the query holds the recipe for producing results until a `foreach` loop, , or runs it. +Many LINQ operators use *deferred execution*. Deferred execution means operators that return a sequence, such as , , and , don't run when you define them. They build the recipe for producing results. A `foreach` loop is one way to run that recipe: :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="DeferredExecution"::: -If you need a snapshot of the current results, call or and store that result. This call triggers *eager evaluation*, which runs the query immediately and stores the results instead of deferring work until later enumeration. For more detail, see [Introduction to LINQ queries](../../linq/get-started/introduction-to-linq-queries.md). +Other operations run a query immediately. Operators that return a single value, such as , , , and , must read the elements when you call them so they can produce that value. Materializing a sequence with or also runs the query immediately. + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ImmediateExecution"::: + +There isn't one single trigger that runs every query. *Eager evaluation*, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as , , , and use eager evaluation. So do and when you need a snapshot of the current results. For more information about deferred execution, see [Introduction to LINQ Queries](../../linq/get-started/introduction-to-linq-queries.md) in the LINQ documentation. + +## Compose queries + +To *compose* a query, start with a base query stored in a variable, then build a more specific query from it. Because execution is deferred, each step describes more of the result. No work happens until you enumerate the final query. + +The following example stores the open work items in one query, then reuses that query to find the highest-priority open items: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ComposeQuerySyntax"::: + +You can also compose queries with method syntax. The next example starts with all open items, then conditionally adds another filter before it selects the titles: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ComposeMethodSyntax"::: + +Composition can use both *eager evaluation* and *deferred execution*. Materialize a shared intermediate result with or when you want that part to run once and stay fixed. Then build another deferred query from the cached results for the final output: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ComposeWithCaching"::: ## Go deeper with LINQ diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs index 793b422cda870..eb77c79e5fe16 100644 --- a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -7,6 +7,7 @@ public static void Main() ChooseCollectionExample(); ArraysExample(); ListChangesExample(); + ListInsertRemoveExample(); DictionaryLookupExample(); CollectionExpressionsExample(); IndexesAndRangesExample(); @@ -55,6 +56,31 @@ private static void ListChangesExample() // } + private static void ListInsertRemoveExample() + { + // + List workItems = ["design", "test"]; + + workItems.Insert(1, "code"); + Console.WriteLine($"Insert middle: {string.Join(", ", workItems)}"); // => Insert middle: design, code, test + + workItems.Insert(0, "plan"); + Console.WriteLine($"Insert front: {string.Join(", ", workItems)}"); // => Insert front: plan, design, code, test + + workItems.InsertRange(workItems.Count, ["review", "deploy"]); + Console.WriteLine($"Insert range at end: {string.Join(", ", workItems)}"); // => Insert range at end: plan, design, code, test, review, deploy + + workItems.RemoveAt(workItems.Count - 1); + Console.WriteLine($"Remove end: {string.Join(", ", workItems)}"); // => Remove end: plan, design, code, test, review + + workItems.RemoveAt(2); + Console.WriteLine($"Remove middle: {string.Join(", ", workItems)}"); // => Remove middle: plan, design, test, review + + workItems.RemoveAt(0); + Console.WriteLine($"Remove front: {string.Join(", ", workItems)}"); // => Remove front: design, test, review + // + } + private static void DictionaryLookupExample() { // @@ -109,8 +135,12 @@ private static void IndexesAndRangesExample() Console.WriteLine($"Last: {phases[^1]}"); // => Last: deploy Console.WriteLine($"Middle: {string.Join(", ", phases[1..3])}"); // => Middle: code, test + Console.WriteLine($"Last two: {string.Join(", ", phases[^2..])}"); // => Last two: test, deploy + Console.WriteLine($"Without ends: {string.Join(", ", phases[1..^1])}"); // => Without ends: code, test + Console.WriteLine($"First two: {string.Join(", ", phases[..2])}"); // => First two: design, code + Console.WriteLine($"From third: {string.Join(", ", phases[2..])}"); // => From third: test, deploy + Console.WriteLine($"All phases: {string.Join(", ", phases[..])}"); // => All phases: design, code, test, deploy Console.WriteLine($"List last: {checklist[^1]}"); // => List last: review // } } - diff --git a/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs index 7c6e5f1cd6b42..ad1087f2aecab 100644 --- a/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs @@ -6,10 +6,17 @@ public static void Main() { QuerySyntaxExample(); MethodSyntaxExample(); + QuerySyntaxClearerExample(); + MethodSyntaxClearerExample(); LambdaExpressionsExample(); + QuerySyntaxLambdaExample(); CommonOperatorsExample(); GroupByExample(); DeferredExecutionExample(); + ImmediateExecutionExample(); + ComposeQuerySyntaxExample(); + ComposeMethodSyntaxExample(); + ComposeWithCachingExample(); } private static void QuerySyntaxExample() @@ -47,6 +54,42 @@ private static void MethodSyntaxExample() // } + private static void QuerySyntaxClearerExample() + { + // + (string Area, int Priority)[] workItems = + [ + ("docs", 2), + ("tests", 1), + ("deploy", 4), + ("api", 1) + ]; + + IEnumerable nextItems = + from item in workItems + let label = $"{item.Area}: P{item.Priority}" + where item.Priority <= 2 + orderby item.Priority, item.Area + select label; + + foreach (string item in nextItems) + { + Console.WriteLine(item); // => api: P1, then tests: P1, then docs: P2 + } + // + } + + private static void MethodSyntaxClearerExample() + { + // + List workItems = ["design", "docs", "deploy", "review"]; + + int count = workItems.Count(item => item.StartsWith('d')); + + Console.WriteLine($"Starts with d: {count}"); // => Starts with d: 3 + // + } + private static void LambdaExpressionsExample() { // @@ -63,6 +106,31 @@ private static void LambdaExpressionsExample() // } + private static void QuerySyntaxLambdaExample() + { + // + string[] workItems = ["docs", "test", "deploy"]; + + IEnumerable querySyntax = + from item in workItems + where item.Length == 4 + select item; + + IEnumerable methodSyntax = + workItems.Where(item => item.Length == 4); + + foreach (string item in querySyntax) + { + Console.WriteLine($"Query syntax: {item}"); // => Query syntax: docs, then Query syntax: test + } + + foreach (string item in methodSyntax) + { + Console.WriteLine($"Method syntax: {item}"); // => Method syntax: docs, then Method syntax: test + } + // + } + private static void CommonOperatorsExample() { // @@ -115,5 +183,105 @@ private static void DeferredExecutionExample() } // } -} + private static void ImmediateExecutionExample() + { + // + List workItems = ["design", "docs"]; + + IEnumerable query = workItems.Where(item => item.StartsWith('d')); + + int count = query.Count(); + Console.WriteLine($"Count before add: {count}"); // => Count before add: 2 + + workItems.Add("deploy"); + + Console.WriteLine($"Stored count: {count}"); // => Stored count: 2 + Console.WriteLine($"Current count: {query.Count()}"); // => Current count: 3 + // + } + + private static void ComposeQuerySyntaxExample() + { + // + (string Title, int Priority, bool IsOpen)[] items = + [ + ("docs", 2, true), + ("tests", 1, true), + ("deploy", 3, false), + ("api", 1, true) + ]; + + IEnumerable<(string Title, int Priority, bool IsOpen)> openItems = + from item in items + where item.IsOpen + select item; + + IEnumerable topOpenItems = + from item in openItems + where item.Priority == 1 + orderby item.Title + select item.Title; + + foreach (string title in topOpenItems) + { + Console.WriteLine(title); // => api, then tests + } + // + } + + private static void ComposeMethodSyntaxExample() + { + // + (string Title, string Area, bool IsOpen)[] items = + [ + ("write docs", "docs", true), + ("fix tests", "tests", true), + ("deploy site", "deploy", false) + ]; + + bool onlyDocs = true; + + IEnumerable<(string Title, string Area, bool IsOpen)> query = + items.Where(item => item.IsOpen); + + if (onlyDocs) + { + query = query.Where(item => item.Area == "docs"); + } + + foreach (string title in query.Select(item => item.Title)) + { + Console.WriteLine(title); // => write docs + } + // + } + + private static void ComposeWithCachingExample() + { + // + List<(string Title, int Priority, bool IsOpen)> items = + [ + ("docs", 2, true), + ("tests", 1, true), + ("deploy", 3, false) + ]; + + List<(string Title, int Priority, bool IsOpen)> openItems = items + .Where(item => item.IsOpen) + .ToList(); + + items.Add(("api", 1, true)); + + IEnumerable cachedTopOpenItems = openItems + .Where(item => item.Priority == 1) + .OrderBy(item => item.Title) + .Select(item => item.Title); + + foreach (string title in cachedTopOpenItems) + { + Console.WriteLine(title); // => tests + } + // + } +} From 5607cf71893a674272d892a5520509fd7013b2d5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 16 Jul 2026 15:24:34 -0400 Subject: [PATCH 5/9] Second edit pass. --- .../fundamentals/statements/collections.md | 34 +++++++++++--- docs/csharp/fundamentals/statements/linq.md | 39 +++++++-------- .../collections-statements/Program.cs | 47 ++++++++++++++++++- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index 132fe9aad1613..881d3d5ed656a 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -17,7 +17,7 @@ A *collection* is an object that stores multiple related values. Each value in a ## Choose a collection shape -Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you need a fixed-size sequence. Use when you need a sequence that can add or remove elements. A *map* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from values between square brackets; [later in this article](#create-collections-with-collection-expressions), you learn the syntax in more detail. +Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you know the number of positions the sequence needs. An array's length can't change after you create it, but you can replace the element stored at an existing position. Use when you need a sequence that can add or remove elements. A *map* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from expressions between square brackets; [later in this article](#create-collections-with-collection-expressions), you learn the syntax in more detail. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ChooseCollection"::: @@ -31,9 +31,13 @@ An array is an ordered collection with a fixed length. You access an array eleme Use `foreach` when you want to read every element in order. Use an index when the position matters, such as when you need the first element, the last element, or the position returned by . +The length of an array is fixed, but the elements in that array can change. Assign a new value to an existing index when the position stays the same but the stored value needs an update: + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ArrayElementUpdate"::: + ## Grow and shrink a sequence with `List` -A stores elements in order and can grow or shrink as your program runs. Use to append an element, to remove a matching element, to test whether an element exists, and to find an element's position. +A stores elements in order and can grow or shrink as your program runs. Use to append an element, to remove a matching element, to test whether an element exists, and to find an element's position. Use the list indexer to replace the value at an existing position. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListChanges"::: @@ -43,7 +47,7 @@ Use to add one element at a spe :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListInsertRemove"::: -Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. If your code frequently inserts or removes at the front, a might be the wrong collection shape. +Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. Big-O notation describes how the work grows as the collection size, `n`, grows. O(1), pronounced "order one," means the operation takes about the same amount of work no matter how many elements the collection contains. O(n), pronounced "order n," means the work grows roughly in proportion to the number of elements. If your code frequently inserts or removes at the front, a might be the wrong collection shape. ## Look up items by key with `Dictionary` @@ -53,21 +57,37 @@ A stores key/value pairs. A *key/ An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. is safer for reads when the key might be missing because it reports both outcomes without throwing an exception. For more information about the indexer operator, see [Member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-) in the language reference. +You can also change the value associated with a key that already exists. When your code knows the dictionary contains the key, assign through the indexer. When the key might or might not exist and you need to react to the current value, use first, then assign the new value: + +:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryUpdates"::: + ## Create collections with collection expressions -A *collection expression* creates a collection from values between square brackets. Beginning with C# 12, collection expressions can create arrays, lists, and other collection types. A *spread element* copies the elements from another collection into the new collection. +A *collection expression* creates a collection from expressions between square brackets. Collection expressions can create arrays, lists, and other collection types. A *spread element* copies the elements from another collection into the new collection. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="CollectionExpressions"::: -Collection expressions keep initialization concise. A collection expression has no type of its own. Because `[...]` is typeless by itself, the compiler converts the same expression into the collection shape the context calls for. The receiving variable or parameter can turn it into an array, a , or another supported collection type. +Collection expressions keep initialization concise. A collection expression has no type of its own. Because `[...]` is typeless by itself, the compiler converts the expression into the collection shape the context calls for. The receiving variable or parameter can turn it into an array, a , or other supported collection type. ## Read from positions with indexes and ranges -An can count from the end of a sequence with `^`, and a can select a slice with `..`. From-end indexes can appear on either side of `..`. If you omit the start index, the range starts at the beginning. If you omit the end index, the range continues through the last element. The range `..` means the entire collection. Arrays support both indexes and ranges. supports indexes, including from-end indexes, but it doesn't support the range operator directly. +An can count from the end of a sequence with `^`, and a can select a slice with `..`. Arrays support both indexes and ranges. + +Use a from-end index with `^` when you want to count backward from the end. The expression `phases[^1]` reads the last element, and `phases[^2]` reads the next-to-last element. + +Use a range `a..b` when you want elements from position `a` up to, but not including, position `b`. The expression `phases[1..3]` creates a new array that contains positions `1` and `2`. + +Use an open-start range `..b` when the slice starts at the beginning. The expression `phases[..2]` returns the first two elements. + +Use an open-end range `a..` when the slice continues through the last element. The expression `phases[2..]` returns the elements from position `2` to the end. + +Use the full range `..` when you want a copy of the whole array. The expression `phases[..]` creates a new array with all the same elements. + + supports the indexer syntax for one element, including from-end indexes such as `checklist[^1]`. It doesn't support the range operator directly. Use when you need a range from a list. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="IndexesAndRanges"::: -Use ranges when the subsection is part of the data you're working with. Use a index when you need one item by position. For more information about indexes and ranges, see the [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) tutorial. +Indexes answer "which single element?" Ranges answer "which contiguous slice?" Use indexes when your code needs one position and ranges when the subsection is part of the data you're working with. For more information about indexes and ranges, see the [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) tutorial. ## See also diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index 195a70672ef25..15fd335655f4f 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -9,21 +9,21 @@ ai-usage: ai-assisted # LINQ queries > [!TIP] -> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about providers, operators, and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md). +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about providers, operators, and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. > > **Coming from another language?** LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query. -*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md). +*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. -## Query data with LINQ +## LINQ query expression syntax The examples in this article read [in-memory collections](collections.md) such as arrays and . A query usually has three parts: specify the data source, describe the result, and enumerate the source to produce the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntax"::: -The query describes the result before the `foreach` loop reads it. Because a query describes the result before it enumerates the source, you can *compose* it: build up a more complex query from smaller operations, and combine it with other queries, before any work runs. +The query expression starts with `from` to name the data source and range variable. The `where` clause keeps only matching elements, `orderby` sorts them, and `select` shapes the result. -## Write the same query with method syntax +## LINQ method syntax *Query syntax* uses clauses such as `from`, `where`, `orderby`, and `select`. *Method syntax* calls LINQ methods directly. For in-memory sequences, the standard LINQ methods are the built-in methods for filtering, projection, sorting, grouping, and related operations. @@ -35,28 +35,28 @@ Query syntax often reads well when the query has several clauses. The `let` clau :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxClearer"::: -Method syntax often reads well for short operations that don't have a query-syntax keyword. For example, returns one value directly: +Method syntax often reads well for short operations. It also reads well for operators that don't have a query-syntax keyword. For example, returns one value directly: :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="MethodSyntaxClearer"::: -## Use lambda expressions in LINQ +## Lambda expressions and LINQ A *lambda expression* is an anonymous function that you can pass as an argument. LINQ method syntax commonly uses lambda expressions to say what each operator should do with each element. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="LambdaExpressions"::: -In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element stays in the result. For more information about lambda expressions, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. +In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md) in the fundamentals. For more information about lambda expression syntax, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. Query-syntax clauses use lambda expressions too. Clauses such as `where`, `orderby`, and `select` compile to method calls that take lambda expressions. The range variable becomes the lambda parameter, and the clause expression becomes the lambda body. Query syntax is a concise way to write those same lambdas: :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxLambda"::: -## Shape data with LINQ methods +## Common LINQ methods -Use to keep only the elements that match a condition. Use to transform each element into a new value; C# calls this operation a *projection*. Use to sort elements. Use aggregation methods such as , , and to combine all elements into a single value. These methods are in the namespace and work with sequences such as . +Use to keep only the elements that match a condition. Use to transform each element into a new value; C# calls this operation a *projection*. Use to sort elements. Use aggregation methods such as , , and to produce a single value from all elements in the data source. These methods are in the namespace and work with sequences such as . > [!NOTE] -> If you know the functional-programming terms, is a *filter*, is a *map* (C# calls it a projection), and aggregation methods such as , , and are a *reduce*. +> If you know the traditional functional-programming terms, is a *filter*, is a *map* (C# calls it a projection), and aggregation methods such as , , and are a *reduce*. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="CommonOperators"::: @@ -68,7 +68,7 @@ Use when the result should contain groups :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: -Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md). +Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. ## Run a query @@ -76,15 +76,17 @@ Many LINQ operators use *deferred execution*. Deferred execution means operators :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="DeferredExecution"::: -Other operations run a query immediately. Operators that return a single value, such as , , , and , must read the elements when you call them so they can produce that value. Materializing a sequence with or also runs the query immediately. +Other operations run a query immediately. Operators that return a single value, such as , , , and , must read the elements when you call them so they can produce that value. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ImmediateExecution"::: -There isn't one single trigger that runs every query. *Eager evaluation*, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as , , , and use eager evaluation. So do and when you need a snapshot of the current results. For more information about deferred execution, see [Introduction to LINQ Queries](../../linq/get-started/introduction-to-linq-queries.md) in the LINQ documentation. +There isn't one single trigger that runs a query. *Eager evaluation*, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as , , , and use eager evaluation. So do and when you need a snapshot of the current results. For more information about deferred execution, see [Introduction to LINQ Queries](../../linq/get-started/introduction-to-linq-queries.md) in the LINQ documentation. ## Compose queries -To *compose* a query, start with a base query stored in a variable, then build a more specific query from it. Because execution is deferred, each step describes more of the result. No work happens until you enumerate the final query. +Real queries often grow from smaller decisions: start with a shared filter, add a sort for one screen, or apply an optional condition from user input. By composing queries, you can keep each step readable and reuse common parts instead of repeating one large query. + +Imagine an app that shows open work items in several places. One view needs all open items, and another view needs only the highest-priority open items. To *compose* a query, start with a base query stored in a variable, then build a more specific query from it. Because execution is deferred, each step describes more of the result. No work happens until you enumerate the final query. The following example stores the open work items in one query, then reuses that query to find the highest-priority open items: @@ -94,18 +96,17 @@ You can also compose queries with method syntax. The next example starts with al :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ComposeMethodSyntax"::: -Composition can use both *eager evaluation* and *deferred execution*. Materialize a shared intermediate result with or when you want that part to run once and stay fixed. Then build another deferred query from the cached results for the final output: +Composition can use either or both *eager evaluation* and *deferred execution*. Materialize a shared intermediate result with or when you want that part to run once and stay fixed. Then build another deferred query from the cached results for the final output: :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ComposeWithCaching"::: -## Go deeper with LINQ +## Explore LINQ in depth -This article uses in-memory collections to teach LINQ syntax and core operators. The LINQ section covers more operators and advanced scenarios, including joins, XML, database providers, dynamic queries, and custom operators. +This article uses in-memory collections to teach LINQ syntax and core operators. For more information about LINQ operators and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. The LINQ section covers joins, XML, database providers, dynamic queries, and custom operators. ## See also - [Collections](collections.md) -- [Language Integrated Query (LINQ)](../../linq/index.md) - [Introduction to LINQ queries](../../linq/get-started/introduction-to-linq-queries.md) - [Write LINQ queries](../../linq/get-started/write-linq-queries.md) - [Standard query operators](../../linq/standard-query-operators/index.md) diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs index eb77c79e5fe16..09102f6bf0e86 100644 --- a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -6,9 +6,11 @@ public static void Main() { ChooseCollectionExample(); ArraysExample(); + ArrayElementUpdateExample(); ListChangesExample(); ListInsertRemoveExample(); DictionaryLookupExample(); + DictionaryUpdatesExample(); CollectionExpressionsExample(); IndexesAndRangesExample(); } @@ -42,6 +44,18 @@ private static void ArraysExample() // } + private static void ArrayElementUpdateExample() + { + // + string[] stages = ["design", "code", "test"]; + + stages[0] = "plan"; + + Console.WriteLine(string.Join(", ", stages)); // => plan, code, test + Console.WriteLine($"Length: {stages.Length}"); // => Length: 3 + // + } + private static void ListChangesExample() { // @@ -49,10 +63,11 @@ private static void ListChangesExample() workItems.Add("review"); workItems.Remove("code"); + workItems[1] = "verify"; - Console.WriteLine(string.Join(", ", workItems)); // => design, test, review + Console.WriteLine(string.Join(", ", workItems)); // => design, verify, review Console.WriteLine($"Has review: {workItems.Contains("review")}"); // => Has review: True - Console.WriteLine($"Index of test: {workItems.IndexOf("test")}"); // => Index of test: 1 + Console.WriteLine($"Index of verify: {workItems.IndexOf("verify")}"); // => Index of verify: 1 // } @@ -115,6 +130,33 @@ private static void DictionaryLookupExample() // } + private static void DictionaryUpdatesExample() + { + // + Dictionary priorities = new() + { + ["docs"] = 2, + ["tests"] = 1 + }; + + priorities["docs"] = 1; + Console.WriteLine($"docs priority: {priorities["docs"]}"); // => docs priority: 1 + + string key = "deploy"; + if (priorities.TryGetValue(key, out int oldPriority)) + { + Console.WriteLine($"{key} changed from {oldPriority} to 3"); + } + else + { + Console.WriteLine($"{key} didn't have a priority yet"); // => deploy didn't have a priority yet + } + + priorities[key] = 3; + Console.WriteLine($"deploy priority: {priorities[key]}"); // => deploy priority: 3 + // + } + private static void CollectionExpressionsExample() { // @@ -141,6 +183,7 @@ private static void IndexesAndRangesExample() Console.WriteLine($"From third: {string.Join(", ", phases[2..])}"); // => From third: test, deploy Console.WriteLine($"All phases: {string.Join(", ", phases[..])}"); // => All phases: design, code, test, deploy Console.WriteLine($"List last: {checklist[^1]}"); // => List last: review + Console.WriteLine($"List first two: {string.Join(", ", checklist.GetRange(0, 2))}"); // => List first two: design, test // } } From 5d0e89b28121b57723f93173e0b59b87f43b8537 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 16 Jul 2026 15:45:01 -0400 Subject: [PATCH 6/9] Final proofread --- docs/csharp/fundamentals/statements/collections.md | 8 +++----- docs/csharp/fundamentals/statements/linq.md | 8 ++++---- .../statements/snippets/collections-statements/Program.cs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index 881d3d5ed656a..010cd2479d4b0 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -49,7 +49,7 @@ Use to add one element at a spe Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. Big-O notation describes how the work grows as the collection size, `n`, grows. O(1), pronounced "order one," means the operation takes about the same amount of work no matter how many elements the collection contains. O(n), pronounced "order n," means the work grows roughly in proportion to the number of elements. If your code frequently inserts or removes at the front, a might be the wrong collection shape. -## Look up items by key with `Dictionary` +## Associate a value by key with `Dictionary` A stores key/value pairs. A *key/value pair* is one key and the value associated with that key; .NET represents one pair with . Use the dictionary indexer to add or update a value by key, and use when the key might not exist. @@ -63,7 +63,7 @@ You can also change the value associated with a key that already exists. When yo ## Create collections with collection expressions -A *collection expression* creates a collection from expressions between square brackets. Collection expressions can create arrays, lists, and other collection types. A *spread element* copies the elements from another collection into the new collection. +A *collection expression* creates a collection from expressions between square brackets. Beginning with C# 12, collection expressions can create arrays, lists, and other collection types. A *spread element* copies the elements from another collection into the new collection. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="CollectionExpressions"::: @@ -71,7 +71,7 @@ Collection expressions keep initialization concise. A collection expression has ## Read from positions with indexes and ranges -An can count from the end of a sequence with `^`, and a can select a slice with `..`. Arrays support both indexes and ranges. +An can count from the end of a sequence with `^`, and a can select a slice with `..` (a sequence of 2 dots). Arrays and `List` support both indexes and ranges. Use a from-end index with `^` when you want to count backward from the end. The expression `phases[^1]` reads the last element, and `phases[^2]` reads the next-to-last element. @@ -83,8 +83,6 @@ Use an open-end range `a..` when the slice continues through the last element. T Use the full range `..` when you want a copy of the whole array. The expression `phases[..]` creates a new array with all the same elements. - supports the indexer syntax for one element, including from-end indexes such as `checklist[^1]`. It doesn't support the range operator directly. Use when you need a range from a list. - :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="IndexesAndRanges"::: Indexes answer "which single element?" Ranges answer "which contiguous slice?" Use indexes when your code needs one position and ranges when the subsection is part of the data you're working with. For more information about indexes and ranges, see the [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) tutorial. diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index 15fd335655f4f..1d5084bb92308 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -35,7 +35,7 @@ Query syntax often reads well when the query has several clauses. The `let` clau :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxClearer"::: -Method syntax often reads well for short operations. It also reads well for operators that don't have a query-syntax keyword. For example, returns one value directly: +Method syntax often reads well for short operations. It's necessary for methods that don't have query-syntax keyword. For example, returns one value directly: :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="MethodSyntaxClearer"::: @@ -45,7 +45,7 @@ A *lambda expression* is an anonymous function that you can pass as an argument. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="LambdaExpressions"::: -In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md) in the fundamentals. For more information about lambda expression syntax, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. +In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md) in the fundamentals. For detailed information about lambda expression syntax, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. Query-syntax clauses use lambda expressions too. Clauses such as `where`, `orderby`, and `select` compile to method calls that take lambda expressions. The range variable becomes the lambda parameter, and the clause expression becomes the lambda body. Query syntax is a concise way to write those same lambdas: @@ -68,7 +68,7 @@ Use when the result should contain groups :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: -Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. +Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. ## Run a query @@ -80,7 +80,7 @@ Other operations run a query immediately. Operators that return a single value, :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="ImmediateExecution"::: -There isn't one single trigger that runs a query. *Eager evaluation*, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as , , , and use eager evaluation. So do and when you need a snapshot of the current results. For more information about deferred execution, see [Introduction to LINQ Queries](../../linq/get-started/introduction-to-linq-queries.md) in the LINQ documentation. +There's no single trigger that runs a query. *Eager evaluation*, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as , , , and use eager evaluation. So do and when you need a snapshot of the current results. For more information about deferred execution, see [Introduction to LINQ Queries](../../linq/get-started/introduction-to-linq-queries.md) in the LINQ documentation. ## Compose queries diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs index 09102f6bf0e86..b7db6f6dd0716 100644 --- a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -183,7 +183,7 @@ private static void IndexesAndRangesExample() Console.WriteLine($"From third: {string.Join(", ", phases[2..])}"); // => From third: test, deploy Console.WriteLine($"All phases: {string.Join(", ", phases[..])}"); // => All phases: design, code, test, deploy Console.WriteLine($"List last: {checklist[^1]}"); // => List last: review - Console.WriteLine($"List first two: {string.Join(", ", checklist.GetRange(0, 2))}"); // => List first two: design, test + Console.WriteLine($"List first two: {string.Join(", ", checklist[0..2])}"); // => List first two: design, test // } } From d7ec0c7bdeffd7e80eadaeebeccd3a6e801333b6 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 20 Jul 2026 15:32:24 -0400 Subject: [PATCH 7/9] Respond to feedback. Respond to review feedback. --- .../fundamentals/statements/collections.md | 27 ++-- docs/csharp/fundamentals/statements/linq.md | 18 ++- .../collections-statements/Program.cs | 138 +++++++++++++----- .../snippets/linq-statements/Program.cs | 84 +++++++++-- 4 files changed, 194 insertions(+), 73 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index 010cd2479d4b0..ac99ebc325137 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -17,7 +17,7 @@ A *collection* is an object that stores multiple related values. Each value in a ## Choose a collection shape -Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you know the number of positions the sequence needs. An array's length can't change after you create it, but you can replace the element stored at an existing position. Use when you need a sequence that can add or remove elements. A *map* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from expressions between square brackets; [later in this article](#create-collections-with-collection-expressions), you learn the syntax in more detail. +Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you know the number of positions the sequence needs. An array's length can't change after you create it, but you can replace the element stored at an existing position. Use when you need a sequence that can add or remove elements. A *dictionary* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from expressions between square brackets. The "Create collections with collection expressions" section explains that syntax in more detail. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ChooseCollection"::: @@ -47,7 +47,10 @@ Use to add one element at a spe :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListInsertRemove"::: -Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. Big-O notation describes how the work grows as the collection size, `n`, grows. O(1), pronounced "order one," means the operation takes about the same amount of work no matter how many elements the collection contains. O(n), pronounced "order n," means the work grows roughly in proportion to the number of elements. If your code frequently inserts or removes at the front, a might be the wrong collection shape. +Adding or removing at the end of a is fast. Adding with is an O(1) operation on average, and removing the last element with is O(1). Inserting or removing at the front or middle is O(n) because every later element shifts to a new index. If your code frequently inserts or removes at the front, a might be the wrong collection shape. + +> [!TIP] +> Big-O notation describes how the work grows as the collection size, `n`, grows. O(1), pronounced "order one," means the operation takes about the same amount of work no matter how many elements the collection contains. O(n), pronounced "order n," means the work grows roughly in proportion to the number of elements in the collection. ## Associate a value by key with `Dictionary` @@ -55,9 +58,9 @@ A stores key/value pairs. A *key/ :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryLookup"::: -An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. is safer for reads when the key might be missing because it reports both outcomes without throwing an exception. For more information about the indexer operator, see [Member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-) in the language reference. +An *indexer* lets you use bracket syntax to access a value from an object. The dictionary indexer is useful when the key must exist or when you're assigning a value. checks whether a key exists and gives you the value when it does. For more information about the indexer operator, see [Member access operators](../../language-reference/operators/member-access-operators.md#indexer-operator-) in the language reference. -You can also change the value associated with a key that already exists. When your code knows the dictionary contains the key, assign through the indexer. When the key might or might not exist and you need to react to the current value, use first, then assign the new value: +You can also change the value associated with a key that already exists. Assign through the indexer to add a key or replace the value for an existing key. If the replacement depends on the current value, read the value first, then assign the new value: :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryUpdates"::: @@ -67,25 +70,21 @@ A *collection expression* creates a collection from expressions between square b :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="CollectionExpressions"::: -Collection expressions keep initialization concise. A collection expression has no type of its own. Because `[...]` is typeless by itself, the compiler converts the expression into the collection shape the context calls for. The receiving variable or parameter can turn it into an array, a , or other supported collection type. +Collection expressions keep initialization concise. A collection expression has no type of its own. The compiler converts the expression into the collection shape the context calls for. The receiving variable or parameter can turn it into an array, a , or other supported collection type. ## Read from positions with indexes and ranges -An can count from the end of a sequence with `^`, and a can select a slice with `..` (a sequence of 2 dots). Arrays and `List` support both indexes and ranges. - -Use a from-end index with `^` when you want to count backward from the end. The expression `phases[^1]` reads the last element, and `phases[^2]` reads the next-to-last element. - -Use a range `a..b` when you want elements from position `a` up to, but not including, position `b`. The expression `phases[1..3]` creates a new array that contains positions `1` and `2`. +Indexes answer "which single element?" Ranges answer "which contiguous slice?" Use indexes when your code needs one element and ranges when the subsection is part of the data you're working with. Arrays and `List` support both indexes and ranges. -Use an open-start range `..b` when the slice starts at the beginning. The expression `phases[..2]` returns the first two elements. +An can count from the start or from the end of a sequence. Use the number directly to count from the start: `phases[0]` reads the first element, and `phases[1]` reads the second element. Use `^` to count from the end: `phases[^1]` reads the last element, and `phases[^2]` reads the next-to-last element. -Use an open-end range `a..` when the slice continues through the last element. The expression `phases[2..]` returns the elements from position `2` to the end. +An selects a slice with `..` (two dots). The range operator isn't the same as the spread element you saw earlier in collection expressions, even though both use dots. Use a range `a..b` when you want elements from index `a` up to, but ***not including***, index `b`. The expression `phases[1..3]` creates a new array that contains the elements at index `1` and index `2`. -Use the full range `..` when you want a copy of the whole array. The expression `phases[..]` creates a new array with all the same elements. +You can omit one or both range indexes. Omit the start index when the slice starts at the beginning, as in `phases[..2]` for the first two elements. Omit the end index when the slice continues through the last element, as in `phases[2..]` for the elements from index `2` to the end. Omit both indexes when you want a copy of the whole array, as in `phases[..]`. A range can mix index kinds. The expression `phases[1..^1]` combines a from-start index and a from-end index to return the middle elements, `code` and `test`. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="IndexesAndRanges"::: -Indexes answer "which single element?" Ranges answer "which contiguous slice?" Use indexes when your code needs one position and ranges when the subsection is part of the data you're working with. For more information about indexes and ranges, see the [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) tutorial. +For more information about indexes and ranges, see [Explore ranges of data using indices and ranges](../../tutorials/ranges-indexes.md) in the tutorials. ## See also diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index 1d5084bb92308..6825e78adfb22 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -9,15 +9,15 @@ ai-usage: ai-assisted # LINQ queries > [!TIP] -> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about providers, operators, and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more information about providers, operators, and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md). > > **Coming from another language?** LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query. -*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. A *sequence* is an ordered set of elements represented by . This article uses in-memory collections for its examples; for more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. +*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. Many LINQ operators use *deferred execution*: they describe the result first and read the data later, when your code asks for the results. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. This article uses in-memory collections for its examples. A *sequence* is an ordered set of elements represented by . For more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. ## LINQ query expression syntax -The examples in this article read [in-memory collections](collections.md) such as arrays and . A query usually has three parts: specify the data source, describe the result, and enumerate the source to produce the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. +The examples in this article read in-memory collections such as arrays and . A query usually has three parts: specify the data source, describe the result, and enumerate the source to produce the result. To *enumerate* a sequence means to read its elements one at a time, often with `foreach`. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntax"::: @@ -45,7 +45,7 @@ A *lambda expression* is an anonymous function that you can pass as an argument. :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="LambdaExpressions"::: -In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md) in the fundamentals. For detailed information about lambda expression syntax, see [Lambda expressions - Lambda expressions and anonymous functions](../../language-reference/operators/lambda-expressions.md) in the language reference. +In `name => name.Length == 3`, `name` is the input element and `name.Length == 3` is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md) in the fundamentals. Query-syntax clauses use lambda expressions too. Clauses such as `where`, `orderby`, and `select` compile to method calls that take lambda expressions. The range variable becomes the lambda parameter, and the clause expression becomes the lambda body. Query syntax is a concise way to write those same lambdas: @@ -53,7 +53,13 @@ Query-syntax clauses use lambda expressions too. Clauses such as `where`, `order ## Common LINQ methods -Use to keep only the elements that match a condition. Use to transform each element into a new value; C# calls this operation a *projection*. Use to sort elements. Use aggregation methods such as , , and to produce a single value from all elements in the data source. These methods are in the namespace and work with sequences such as . +The most common LINQ methods cover the everyday steps of a query: filter data, shape results, sort, group, and summarize. These methods are in the namespace and work with sequences such as . + +- keeps only the elements that match a condition. +- transforms each element into a new value. C# calls this operation a *projection*. +- sorts elements. +- creates groups of elements that share a key. +- Aggregation methods such as , , and produce a single value from all elements in the data source. > [!NOTE] > If you know the traditional functional-programming terms, is a *filter*, is a *map* (C# calls it a projection), and aggregation methods such as , , and are a *reduce*. @@ -68,7 +74,7 @@ Use when the result should contain groups :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: -Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see the [Language Integrated Query (LINQ)](../../linq/index.md) section. +Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. ## Run a query diff --git a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs index b7db6f6dd0716..2b304f5fd33a0 100644 --- a/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -28,9 +28,15 @@ private static void ChooseCollectionExample() backlog.Add("test"); - Console.WriteLine($"Array: {string.Join(", ", sprintPlan)}"); // => Array: design, code, test - Console.WriteLine($"List count: {backlog.Count}"); // => List count: 3 - Console.WriteLine($"Priority for docs: {priorities["docs"]}"); // => Priority for docs: 2 + Console.WriteLine($"Array: {string.Join(", ", sprintPlan)}"); + Console.WriteLine($"List count: {backlog.Count}"); + Console.WriteLine($"Priority for docs: {priorities["docs"]}"); + // This example produces the following output: + // + // Array: design, code, test + // List count: 3 + // Priority for docs: 2 + // // } @@ -39,8 +45,13 @@ private static void ArraysExample() // string[] stages = ["design", "code", "test", "review"]; - Console.WriteLine($"First: {stages[0]}"); // => First: design - Console.WriteLine($"test index: {Array.IndexOf(stages, "test")}"); // => test index: 2 + Console.WriteLine($"First: {stages[0]}"); + Console.WriteLine($"test index: {Array.IndexOf(stages, "test")}"); + // This example produces the following output: + // + // First: design + // test index: 2 + // // } @@ -51,8 +62,13 @@ private static void ArrayElementUpdateExample() stages[0] = "plan"; - Console.WriteLine(string.Join(", ", stages)); // => plan, code, test - Console.WriteLine($"Length: {stages.Length}"); // => Length: 3 + Console.WriteLine(string.Join(", ", stages)); + Console.WriteLine($"Length: {stages.Length}"); + // This example produces the following output: + // + // plan, code, test + // Length: 3 + // // } @@ -65,9 +81,15 @@ private static void ListChangesExample() workItems.Remove("code"); workItems[1] = "verify"; - Console.WriteLine(string.Join(", ", workItems)); // => design, verify, review - Console.WriteLine($"Has review: {workItems.Contains("review")}"); // => Has review: True - Console.WriteLine($"Index of verify: {workItems.IndexOf("verify")}"); // => Index of verify: 1 + Console.WriteLine(string.Join(", ", workItems)); + Console.WriteLine($"Has review: {workItems.Contains("review")}"); + Console.WriteLine($"Index of verify: {workItems.IndexOf("verify")}"); + // This example produces the following output: + // + // design, verify, review + // Has review: True + // Index of verify: 1 + // // } @@ -77,22 +99,31 @@ private static void ListInsertRemoveExample() List workItems = ["design", "test"]; workItems.Insert(1, "code"); - Console.WriteLine($"Insert middle: {string.Join(", ", workItems)}"); // => Insert middle: design, code, test + Console.WriteLine($"Insert middle: {string.Join(", ", workItems)}"); workItems.Insert(0, "plan"); - Console.WriteLine($"Insert front: {string.Join(", ", workItems)}"); // => Insert front: plan, design, code, test + Console.WriteLine($"Insert front: {string.Join(", ", workItems)}"); workItems.InsertRange(workItems.Count, ["review", "deploy"]); - Console.WriteLine($"Insert range at end: {string.Join(", ", workItems)}"); // => Insert range at end: plan, design, code, test, review, deploy + Console.WriteLine($"Insert range at end: {string.Join(", ", workItems)}"); workItems.RemoveAt(workItems.Count - 1); - Console.WriteLine($"Remove end: {string.Join(", ", workItems)}"); // => Remove end: plan, design, code, test, review + Console.WriteLine($"Remove end: {string.Join(", ", workItems)}"); workItems.RemoveAt(2); - Console.WriteLine($"Remove middle: {string.Join(", ", workItems)}"); // => Remove middle: plan, design, test, review + Console.WriteLine($"Remove middle: {string.Join(", ", workItems)}"); workItems.RemoveAt(0); - Console.WriteLine($"Remove front: {string.Join(", ", workItems)}"); // => Remove front: design, test, review + Console.WriteLine($"Remove front: {string.Join(", ", workItems)}"); + // This example produces the following output: + // + // Insert middle: design, code, test + // Insert front: plan, design, code, test + // Insert range at end: plan, design, code, test, review, deploy + // Remove end: plan, design, code, test, review + // Remove middle: plan, design, test, review + // Remove front: design, test, review + // // } @@ -110,7 +141,7 @@ private static void DictionaryLookupExample() if (priorities.TryGetValue("docs", out int docsPriority)) { - Console.WriteLine($"docs priority: {docsPriority}"); // => docs priority: 2 + Console.WriteLine($"docs priority: {docsPriority}"); } else { @@ -123,10 +154,16 @@ private static void DictionaryLookupExample() } else { - Console.WriteLine("deploy missing"); // => deploy missing + Console.WriteLine("deploy missing"); } - Console.WriteLine($"count: {priorities.Count}"); // => count: 2 + Console.WriteLine($"count: {priorities.Count}"); + // This example produces the following output: + // + // docs priority: 2 + // deploy missing + // count: 2 + // // } @@ -140,20 +177,23 @@ private static void DictionaryUpdatesExample() }; priorities["docs"] = 1; - Console.WriteLine($"docs priority: {priorities["docs"]}"); // => docs priority: 1 - string key = "deploy"; - if (priorities.TryGetValue(key, out int oldPriority)) + if (priorities.TryGetValue("tests", out int testsPriority)) { - Console.WriteLine($"{key} changed from {oldPriority} to 3"); - } - else - { - Console.WriteLine($"{key} didn't have a priority yet"); // => deploy didn't have a priority yet + priorities["tests"] = testsPriority + 1; } - priorities[key] = 3; - Console.WriteLine($"deploy priority: {priorities[key]}"); // => deploy priority: 3 + priorities["deploy"] = 3; + + Console.WriteLine($"docs priority: {priorities["docs"]}"); + Console.WriteLine($"tests priority: {priorities["tests"]}"); + Console.WriteLine($"deploy priority: {priorities["deploy"]}"); + // This example produces the following output: + // + // docs priority: 1 + // tests priority: 2 + // deploy priority: 3 + // // } @@ -161,11 +201,17 @@ private static void CollectionExpressionsExample() { // string[] planned = ["design", "code"]; + // The spread element .. planned copies each element from planned. string[] upcoming = [.. planned, "test"]; List blocked = ["docs"]; - Console.WriteLine($"Upcoming: {string.Join(", ", upcoming)}"); // => Upcoming: design, code, test - Console.WriteLine($"Blocked: {string.Join(", ", blocked)}"); // => Blocked: docs + Console.WriteLine($"Upcoming: {string.Join(", ", upcoming)}"); + Console.WriteLine($"Blocked: {string.Join(", ", blocked)}"); + // This example produces the following output: + // + // Upcoming: design, code, test + // Blocked: docs + // // } @@ -175,15 +221,27 @@ private static void IndexesAndRangesExample() string[] phases = ["design", "code", "test", "deploy"]; List checklist = ["design", "test", "review"]; - Console.WriteLine($"Last: {phases[^1]}"); // => Last: deploy - Console.WriteLine($"Middle: {string.Join(", ", phases[1..3])}"); // => Middle: code, test - Console.WriteLine($"Last two: {string.Join(", ", phases[^2..])}"); // => Last two: test, deploy - Console.WriteLine($"Without ends: {string.Join(", ", phases[1..^1])}"); // => Without ends: code, test - Console.WriteLine($"First two: {string.Join(", ", phases[..2])}"); // => First two: design, code - Console.WriteLine($"From third: {string.Join(", ", phases[2..])}"); // => From third: test, deploy - Console.WriteLine($"All phases: {string.Join(", ", phases[..])}"); // => All phases: design, code, test, deploy - Console.WriteLine($"List last: {checklist[^1]}"); // => List last: review - Console.WriteLine($"List first two: {string.Join(", ", checklist[0..2])}"); // => List first two: design, test + Console.WriteLine($"Last: {phases[^1]}"); + Console.WriteLine($"Middle: {string.Join(", ", phases[1..3])}"); + Console.WriteLine($"Last two: {string.Join(", ", phases[^2..])}"); + Console.WriteLine($"Without ends: {string.Join(", ", phases[1..^1])}"); + Console.WriteLine($"First two: {string.Join(", ", phases[..2])}"); + Console.WriteLine($"From third: {string.Join(", ", phases[2..])}"); + Console.WriteLine($"All phases: {string.Join(", ", phases[..])}"); + Console.WriteLine($"List last: {checklist[^1]}"); + Console.WriteLine($"List first two: {string.Join(", ", checklist[0..2])}"); + // This example produces the following output: + // + // Last: deploy + // Middle: code, test + // Last two: test, deploy + // Without ends: code, test + // First two: design, code + // From third: test, deploy + // All phases: design, code, test, deploy + // List last: review + // List first two: design, test + // // } } diff --git a/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs index ad1087f2aecab..440ba6147a9d1 100644 --- a/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs @@ -32,8 +32,13 @@ orderby name foreach (string name in query) { - Console.WriteLine(name); // => Cleo, then Dara + Console.WriteLine(name); } + // This example produces the following output: + // + // Cleo + // Dara + // // } @@ -49,8 +54,13 @@ private static void MethodSyntaxExample() foreach (string name in query) { - Console.WriteLine(name); // => Cleo, then Dara + Console.WriteLine(name); } + // This example produces the following output: + // + // Cleo + // Dara + // // } @@ -74,8 +84,14 @@ from item in workItems foreach (string item in nextItems) { - Console.WriteLine(item); // => api: P1, then tests: P1, then docs: P2 + Console.WriteLine(item); } + // This example produces the following output: + // + // api: P1 + // tests: P1 + // docs: P2 + // // } @@ -101,8 +117,13 @@ private static void LambdaExpressionsExample() foreach (string name in shortNames) { - Console.WriteLine(name); // => ANA, then BEN + Console.WriteLine(name); } + // This example produces the following output: + // + // ANA + // BEN + // // } @@ -121,13 +142,20 @@ from item in workItems foreach (string item in querySyntax) { - Console.WriteLine($"Query syntax: {item}"); // => Query syntax: docs, then Query syntax: test + Console.WriteLine($"Result: {item}"); } foreach (string item in methodSyntax) { - Console.WriteLine($"Method syntax: {item}"); // => Method syntax: docs, then Method syntax: test + Console.WriteLine($"Result: {item}"); } + // This example produces the following output: + // + // Result: docs + // Result: test + // Result: docs + // Result: test + // // } @@ -149,8 +177,14 @@ private static void CommonOperatorsExample() foreach (string item in plannedWork) { - Console.WriteLine(item); // => Code: 1, then Docs: 2, then Test: 3 + Console.WriteLine(item); } + // This example produces the following output: + // + // Code: 1 + // Docs: 2 + // Test: 3 + // // } @@ -163,8 +197,13 @@ private static void GroupByExample() foreach (IGrouping group in groups) { - Console.WriteLine($"{group.Key}: {string.Join(", ", group)}"); // => a: api, auth, then d: docs, deploy + Console.WriteLine($"{group.Key}: {string.Join(", ", group)}"); } + // This example produces the following output: + // + // a: api, auth + // d: docs, deploy + // // } @@ -177,10 +216,17 @@ private static void DeferredExecutionExample() workItems.Add("deploy"); + // The query runs here, when foreach asks for the results. foreach (string item in query) { - Console.WriteLine(item); // => design, then docs, then deploy + Console.WriteLine(item); } + // This example produces the following output: + // + // design + // docs + // deploy + // // } @@ -191,13 +237,20 @@ private static void ImmediateExecutionExample() IEnumerable query = workItems.Where(item => item.StartsWith('d')); + // Count() reads the matching elements right when this line runs. int count = query.Count(); - Console.WriteLine($"Count before add: {count}"); // => Count before add: 2 + Console.WriteLine($"Count before add: {count}"); workItems.Add("deploy"); - Console.WriteLine($"Stored count: {count}"); // => Stored count: 2 - Console.WriteLine($"Current count: {query.Count()}"); // => Current count: 3 + Console.WriteLine($"Stored count: {count}"); + Console.WriteLine($"Current count: {query.Count()}"); + // This example produces the following output: + // + // Count before add: 2 + // Stored count: 2 + // Current count: 3 + // // } @@ -225,8 +278,13 @@ orderby item.Title foreach (string title in topOpenItems) { - Console.WriteLine(title); // => api, then tests + Console.WriteLine(title); } + // This example produces the following output: + // + // api + // tests + // // } From d490809ed5d0ad3bf9dc851526401653fc980d33 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 20 Jul 2026 17:26:02 -0400 Subject: [PATCH 8/9] Address second-round review: restore collection-expression link; fix LINQ cross-reference wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/csharp/fundamentals/statements/collections.md | 2 +- docs/csharp/fundamentals/statements/linq.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md index ac99ebc325137..8c7ab426451f6 100644 --- a/docs/csharp/fundamentals/statements/collections.md +++ b/docs/csharp/fundamentals/statements/collections.md @@ -17,7 +17,7 @@ A *collection* is an object that stores multiple related values. Each value in a ## Choose a collection shape -Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you know the number of positions the sequence needs. An array's length can't change after you create it, but you can replace the element stored at an existing position. Use when you need a sequence that can add or remove elements. A *dictionary* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from expressions between square brackets. The "Create collections with collection expressions" section explains that syntax in more detail. +Choose the collection that matches how your code uses the data. A *sequence* stores elements in order so you can reach them by position. Use an *array*, which is represented by , when you know the number of positions the sequence needs. An array's length can't change after you create it, but you can replace the element stored at an existing position. Use when you need a sequence that can add or remove elements. A *dictionary* stores values that you reach by key instead of by position. Use when each element has a lookup key, such as a name, ID, or code. The examples use a *collection expression*, which creates a collection from expressions between square brackets. The [Create collections with collection expressions](#create-collections-with-collection-expressions) section explains that syntax in more detail. :::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ChooseCollection"::: diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index 6825e78adfb22..95ae421e1ef09 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -74,7 +74,7 @@ Use when the result should contain groups :::code language="csharp" source="./snippets/linq-statements/Program.cs" id="GroupBy"::: -Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. +Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md). ## Run a query From dc0075e23cd9a6e71878fb1bb6626e3e0905e33f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 20 Jul 2026 17:35:15 -0400 Subject: [PATCH 9/9] Address review consistency: stop calling the linked LINQ article a 'section' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/csharp/fundamentals/statements/linq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/fundamentals/statements/linq.md b/docs/csharp/fundamentals/statements/linq.md index 95ae421e1ef09..bb62509640a51 100644 --- a/docs/csharp/fundamentals/statements/linq.md +++ b/docs/csharp/fundamentals/statements/linq.md @@ -13,7 +13,7 @@ ai-usage: ai-assisted > > **Coming from another language?** LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query. -*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. Many LINQ operators use *deferred execution*: they describe the result first and read the data later, when your code asks for the results. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. This article uses in-memory collections for its examples. A *sequence* is an ordered set of elements represented by . For more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. +*Language Integrated Query (LINQ)* is the C# feature set for querying data with C# syntax. Many LINQ operators use *deferred execution*: they describe the result first and read the data later, when your code asks for the results. A *query* describes which data to read and how to shape the result. A query reads from a *data source*. A data source can be an in-memory collection, such as an array or , or an external source, such as a database or XML, exposed through a LINQ provider. A *LINQ provider* is a library that connects LINQ syntax to a specific kind of data source. This article uses in-memory collections for its examples. A *sequence* is an ordered set of elements represented by . For more information about provider-based queries, see [Language Integrated Query (LINQ)](../../linq/index.md). ## LINQ query expression syntax @@ -108,7 +108,7 @@ Composition can use either or both *eager evaluation* and *deferred execution*. ## Explore LINQ in depth -This article uses in-memory collections to teach LINQ syntax and core operators. For more information about LINQ operators and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md) in the LINQ section. The LINQ section covers joins, XML, database providers, dynamic queries, and custom operators. +This article uses in-memory collections to teach LINQ syntax and core operators. For more information about LINQ operators and advanced scenarios, see [Language Integrated Query (LINQ)](../../linq/index.md). That article covers joins, XML, database providers, dynamic queries, and custom operators. ## See also