Skip to content

[Everyday C#] Phase E, PR 14a: Statements: collections + LINQ#54807

Open
BillWagner wants to merge 6 commits into
dotnet:mainfrom
BillWagner:squad/53556-statements-collections-linq
Open

[Everyday C#] Phase E, PR 14a: Statements: collections + LINQ#54807
BillWagner wants to merge 6 commits into
dotnet:mainfrom
BillWagner:squad/53556-statements-collections-linq

Conversation

@BillWagner

@BillWagner BillWagner commented Jul 15, 2026

Copy link
Copy Markdown
Member

Everyday C# — Phase E, PR 14a: Statements: collections + LINQ

Closes #53556

Adds two example-heavy Fundamentals concept articles under fundamentals/statements/, each backed by a compiling net10.0 snippet project, and wires them into the TOC. Every code sample is included from an external snippet via :::code regions and shows its result as a trailing // => comment; the samples saturate the everyday feature set (collection expressions, file-scoped namespaces, nullable enabled, target-typed new).

New articles

  • fundamentals/statements/collections.md — arrays, List<T>, Dictionary<TKey,TValue>; adding, removing, and searching elements; collection expressions (C# 12); indexes and ranges (C# 8) applied to collections. Introduces collection, element, sequence vs map, generic type / type safety, index, range, and collection expression at first use.
  • fundamentals/statements/linq.md — query syntax and method (fluent) syntax; the functional trio filter / map / reduce mapped to Where / Select / Aggregate·Sum·Count, plus OrderBy and GroupBy; lambda (anonymous function) expressions in LINQ; deferred execution vs eager evaluation. Names LINQ providers as a first-class use and links out for provider-based queries.

TOC

Collections and LINQ added under Fundamentals → Expressions and statements, after Iteration statements.

Scope discipline — deliberately left out of Fundamentals (link-out targets)

No existing content was cut (both articles are new); the following advanced topics are intentionally kept out of Fundamentals and linked to their proper homes, per Goal 11 / Filter A:

  • collections.mdlanguage-reference/builtin-types/arrays.md / collections.md → multidimensional arrays, covariance, collection taxonomy are reference-level.
  • collections.mdlanguage-reference/operators/collection-expressions.md / member-access-operators.md → conversion/spread rules and full []/^/.. operator rules are reference-level.
  • collections.mdstandard/collections/* → choosing among specialized/concurrent/immutable collections and custom comparers fail universality.
  • linq.mdlinq/* (LINQ section) → joins, providers/IQueryable, deferred-execution mechanics, custom operators, XML/EF behavior are LINQ-focus material.
  • linq.md → PLINQ / expression trees / performance → parallel and advanced-representation material.

Validation

  • Both snippet projects build clean (0 warnings, 0 errors) on net10.0; every // => output verified by running the programs.
  • All relative links, the #indexer-operator- anchor, and all <xref:...> UIDs verified to resolve; version claims (collection expressions → C# 12, ^/.. → C# 8) confirmed.
  • No programming-guide/* link destinations (that area is slated for retirement).

Opened as draft for review.


Internal previews

📄 File 🔗 Preview link
docs/csharp/fundamentals/statements/collections.md Common collection types
docs/csharp/fundamentals/statements/linq.md "LINQ queries in C#"
docs/csharp/toc.yml Taken from https://github.com/dotnet/roslyn/wiki/Samples-and-Walkthroughs

Phase E, PR 14a. Adds two example-heavy Fundamentals concept articles
under fundamentals/statements/ with compiling net10.0 snippet projects:

- collections.md: arrays, List<T>, Dictionary<TKey,TValue>, 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 dotnet#53556

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7f9f7f68-82b8-43d2-a511-1a4f07138403
Copilot AI review requested due to automatic review settings July 15, 2026 18:36
@dotnetrepoman dotnetrepoman Bot added this to the July 2026 milestone Jul 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new “Everyday C#” Fundamentals concept articles under fundamentals/statements/ (Collections and LINQ), backed by new compiling snippet projects, and wires both pages into the C# TOC.

Changes:

  • Adds new Fundamentals articles: Collections and LINQ queries.
  • Adds new snippet projects (collections-statements and linq-statements) that supply all referenced :::code regions.
  • Updates docs/csharp/toc.yml to include the new pages under Fundamentals → Expressions and statements.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
docs/csharp/toc.yml Adds TOC entries for the new Collections and LINQ Fundamentals pages.
docs/csharp/fundamentals/statements/collections.md New concept article introducing arrays, List<T>, dictionaries, collection expressions, and indexes/ranges with snippet-backed examples.
docs/csharp/fundamentals/statements/linq.md New concept article introducing LINQ query/method syntax, common operators, grouping, and deferred execution with snippet-backed examples.
docs/csharp/fundamentals/statements/snippets/collections-statements/Program.cs Snippet source for the Collections article :::code regions.
docs/csharp/fundamentals/statements/snippets/collections-statements/collections-statements.csproj New net10.0 snippet project for Collections examples.
docs/csharp/fundamentals/statements/snippets/linq-statements/Program.cs Snippet source for the LINQ article :::code regions.
docs/csharp/fundamentals/statements/snippets/linq-statements/linq-statements.csproj New net10.0 snippet project for LINQ examples.

Comment thread docs/csharp/fundamentals/statements/linq.md Outdated
BillWagner and others added 5 commits July 15, 2026 14:57
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
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7f9f7f68-82b8-43d2-a511-1a4f07138403
This is looking a little better.
@BillWagner
BillWagner marked this pull request as ready for review July 16, 2026 19:45
@BillWagner
BillWagner requested a review from a team as a code owner July 16, 2026 19:45

@adegeo adegeo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good! I wrote my comments as if talking to LLM, so they're curt.

Comment on lines +31 to +33
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the way you actually wanted the comments? Or did you mean to do what the other docs do

        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
        //


## 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 <xref:System.Array>, 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 <xref:System.Collections.Generic.List`1> 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 <xref:System.Collections.Generic.Dictionary`2> 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "map" what we want people to use when referring to dictionaries? Because we're using it here like it is. In the note near the intro we say "Map or dictionary" to at least bring the user into the thought of "I know what a map is, oh it's called dictionary here." Continuing to use Map seems wrong.


:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="ListInsertRemove":::

Adding or removing at the end of a <xref:System.Collections.Generic.List`1> is fast. Adding with <xref:System.Collections.Generic.List`1.Add*> is an O(1) operation on average, and removing the last element with <xref:System.Collections.Generic.List`1.RemoveAt*> 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 <xref:System.Collections.Generic.List`1> might be the wrong collection shape.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Big-O notation should be a tip note.

Is the description of how O(n) is working correct? Wouldn't it be that it grows roughly in proportion to how many elements are ahead of the insertion index?

Comment on lines +58 to +62
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. <xref:System.Collections.Generic.Dictionary`2.TryGetValue*> 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 <xref:System.Collections.Generic.Dictionary`2.TryGetValue*> first, then assign the new value:

:::code language="csharp" source="./snippets/collections-statements/Program.cs" id="DictionaryUpdates":::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about all of this. It's kind of repeating (content and code) from above.

Also, it says "it's safer to use TryGetValue because it does XYZ" but it doesn't say safer than what. So does it add value? Is it that important to call out? The TryGet pattern is there to be helpful and more efficient than the Contains+Get combination. And Contains+Get is there to prevent you from using Get when the key doesn't exist and thus crashing or having to handle the exception. But then you need to explain all of that and it seems more than the scope of this article. I suggest just demonstrating TryGetValue in the code, but not going into details as to why this was chosen.


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":::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a comment in this referenced code that calls out the spread. People may not know what spread looks like.


:::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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two big links feel like too much. Is one better than the other? For this small paragraph, 70% of it is text about "for more information" 😁


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":::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this reference code example, having both query and method syntax console output being the same exact values would be helpful. This way it demonstrates that both versions of the syntax produced the same results.


## Common LINQ methods

Use <xref:System.Linq.Enumerable.Where*> to keep only the elements that match a condition. Use <xref:System.Linq.Enumerable.Select*> to transform each element into a new value; C# calls this operation a *projection*. Use <xref:System.Linq.Enumerable.OrderBy*> to sort elements. Use aggregation methods such as <xref:System.Linq.Enumerable.Sum*>, <xref:System.Linq.Enumerable.Count*>, and <xref:System.Linq.Enumerable.Aggregate*> to produce a single value from all elements in the data source. These methods are in the <xref:System.Linq> namespace and work with sequences such as <xref:System.Collections.Generic.IEnumerable`1>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a small intro paragraph and a bullet point list of items.

GroupBy is a subsection, which is OK, but it should be listed here too.


## Run a query

Many LINQ operators use *deferred execution*. Deferred execution means operators that return a sequence, such as <xref:System.Linq.Enumerable.Where*>, <xref:System.Linq.Enumerable.Select*>, and <xref:System.Linq.Enumerable.OrderBy*>, don't run when you define them. They build the recipe for producing results. A `foreach` loop is one way to run that recipe:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is really important to also call out at the start of the article. If someone doesn't make it this far, hopefully they got it up front.

Comment on lines +77 to +81
:::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 <xref:System.Linq.Enumerable.Count*>, <xref:System.Linq.Enumerable.Sum*>, <xref:System.Linq.Enumerable.First*>, and <xref:System.Linq.Enumerable.Any*>, 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":::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these code snippets should call out in a comment when the deferred evaluation happens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Everyday C#] - Phase E, PR 14a. Statements: collections + LINQ

3 participants