diff --git a/docs/csharp/fundamentals/statements/collections.md b/docs/csharp/fundamentals/statements/collections.md new file mode 100644 index 0000000000000..8c7ab426451f6 --- /dev/null +++ b/docs/csharp/fundamentals/statements/collections.md @@ -0,0 +1,97 @@ +--- +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 +--- + +# 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 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*. 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 + +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"::: + + 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 + +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 . + +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. Use the list indexer to replace the value at an existing 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. + +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. + +> [!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` + +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. 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. 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"::: + +## Create collections with collection expressions + +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"::: + +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 + +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. + +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. + +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`. + +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"::: + +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 + +- [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..bb62509640a51 --- /dev/null +++ b/docs/csharp/fundamentals/statements/linq.md @@ -0,0 +1,120 @@ +--- +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 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. 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 + +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"::: + +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. + +## 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. + +:::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. + +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. 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"::: + +## 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 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: + +:::code language="csharp" source="./snippets/linq-statements/Program.cs" id="QuerySyntaxLambda"::: + +## Common LINQ methods + +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*. + +:::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 more information about joins, nested groupings, and provider-specific behavior, see [Language Integrated Query (LINQ)](../../linq/index.md). + +## Run a query + +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"::: + +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'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 + +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: + +:::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 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"::: + +## 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). That article covers joins, XML, database providers, dynamic queries, and custom operators. + +## See also + +- [Collections](collections.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..2b304f5fd33a0 --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs @@ -0,0 +1,247 @@ +namespace CollectionStatements; + +public static class Program +{ + public static void Main() + { + ChooseCollectionExample(); + ArraysExample(); + ArrayElementUpdateExample(); + ListChangesExample(); + ListInsertRemoveExample(); + DictionaryLookupExample(); + DictionaryUpdatesExample(); + 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)}"); + 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 + // + // + } + + private static void ArraysExample() + { + // + string[] stages = ["design", "code", "test", "review"]; + + Console.WriteLine($"First: {stages[0]}"); + Console.WriteLine($"test index: {Array.IndexOf(stages, "test")}"); + // This example produces the following output: + // + // First: design + // test index: 2 + // + // + } + + private static void ArrayElementUpdateExample() + { + // + string[] stages = ["design", "code", "test"]; + + stages[0] = "plan"; + + Console.WriteLine(string.Join(", ", stages)); + Console.WriteLine($"Length: {stages.Length}"); + // This example produces the following output: + // + // plan, code, test + // Length: 3 + // + // + } + + private static void ListChangesExample() + { + // + List workItems = ["design", "code", "test"]; + + workItems.Add("review"); + workItems.Remove("code"); + workItems[1] = "verify"; + + 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 + // + // + } + + private static void ListInsertRemoveExample() + { + // + List workItems = ["design", "test"]; + + workItems.Insert(1, "code"); + Console.WriteLine($"Insert middle: {string.Join(", ", workItems)}"); + + workItems.Insert(0, "plan"); + Console.WriteLine($"Insert front: {string.Join(", ", workItems)}"); + + workItems.InsertRange(workItems.Count, ["review", "deploy"]); + Console.WriteLine($"Insert range at end: {string.Join(", ", workItems)}"); + + workItems.RemoveAt(workItems.Count - 1); + Console.WriteLine($"Remove end: {string.Join(", ", workItems)}"); + + workItems.RemoveAt(2); + Console.WriteLine($"Remove middle: {string.Join(", ", workItems)}"); + + workItems.RemoveAt(0); + 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 + // + // + } + + 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}"); + } + else + { + Console.WriteLine("docs missing"); + } + + if (priorities.TryGetValue("deploy", out int deployPriority)) + { + Console.WriteLine($"deploy priority: {deployPriority}"); + } + else + { + Console.WriteLine("deploy missing"); + } + + Console.WriteLine($"count: {priorities.Count}"); + // This example produces the following output: + // + // docs priority: 2 + // deploy missing + // count: 2 + // + // + } + + private static void DictionaryUpdatesExample() + { + // + Dictionary priorities = new() + { + ["docs"] = 2, + ["tests"] = 1 + }; + + priorities["docs"] = 1; + + if (priorities.TryGetValue("tests", out int testsPriority)) + { + priorities["tests"] = testsPriority + 1; + } + + 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 + // + // + } + + 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)}"); + Console.WriteLine($"Blocked: {string.Join(", ", blocked)}"); + // This example produces the following output: + // + // Upcoming: design, code, test + // Blocked: docs + // + // + } + + private static void IndexesAndRangesExample() + { + // + string[] phases = ["design", "code", "test", "deploy"]; + List checklist = ["design", "test", "review"]; + + 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/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..440ba6147a9d1 --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs @@ -0,0 +1,345 @@ +namespace LinqStatements; + +public static class Program +{ + public static void Main() + { + QuerySyntaxExample(); + MethodSyntaxExample(); + QuerySyntaxClearerExample(); + MethodSyntaxClearerExample(); + LambdaExpressionsExample(); + QuerySyntaxLambdaExample(); + CommonOperatorsExample(); + GroupByExample(); + DeferredExecutionExample(); + ImmediateExecutionExample(); + ComposeQuerySyntaxExample(); + ComposeMethodSyntaxExample(); + ComposeWithCachingExample(); + } + + 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); + } + // This example produces the following output: + // + // Cleo + // 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); + } + // This example produces the following output: + // + // Cleo + // Dara + // + // + } + + 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); + } + // This example produces the following output: + // + // api: P1 + // tests: P1 + // 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() + { + // + 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); + } + // This example produces the following output: + // + // ANA + // BEN + // + // + } + + 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($"Result: {item}"); + } + + foreach (string item in methodSyntax) + { + Console.WriteLine($"Result: {item}"); + } + // This example produces the following output: + // + // Result: docs + // Result: test + // Result: docs + // Result: test + // + // + } + + 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); + } + // This example produces the following output: + // + // Code: 1 + // Docs: 2 + // 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)}"); + } + // This example produces the following output: + // + // a: api, auth + // d: docs, deploy + // + // + } + + private static void DeferredExecutionExample() + { + // + List workItems = ["design", "docs"]; + + IEnumerable query = workItems.Where(item => item.StartsWith('d')); + + workItems.Add("deploy"); + + // The query runs here, when foreach asks for the results. + foreach (string item in query) + { + Console.WriteLine(item); + } + // This example produces the following output: + // + // design + // docs + // deploy + // + // + } + + private static void ImmediateExecutionExample() + { + // + List workItems = ["design", "docs"]; + + 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}"); + + workItems.Add("deploy"); + + 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 + // + // + } + + 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); + } + // This example produces the following output: + // + // api + // 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 + } + // + } +} 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