Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refresh LINQ docs to latest semantics #10113

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

DickBaker
Copy link
Contributor

current LINQ docs C# samples are vintage 2019 and need refresh (Issue #10110 relates)
obviously .NET and C# have evolved apace since, so imho opportune to modernise docs too for best guidance

This PR is intended to explain my cryptic notes in Issue #10110, and to propose a useful refresh of snippet code samples

Note that Allow edits and access to secrets by maintainers is checked so I will be happy for MS wonks to edit, e.g.

  1. ignore the .editorconfig that I used to enforce some consistency across the projects
  2. dispense with the Linq.sln file that is just there [for my VS convenience and in case helpful for eval]
  3. squash my intermediate commits

hopefully the time & effort I expended [as challenge to me!] will yield worthy update to the MS docs
good hunting !

@DickBaker DickBaker requested a review from a team as a code owner July 15, 2024 21:07
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jul 15, 2024
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jul 15, 2024
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

This comment was marked as outdated.

This comment was marked as outdated.

@DickBaker
Copy link
Contributor Author

when I committed PR #10113 I got a "Run snippets 5000 for PR" warning due to file(s) without encompassing .csproj although the PR seemed to have gone through to review stage.

Anyway this iteration is as fix [that step now passes]. Please accept & apply. Thanks.

Copy link

Learn Build status updates of commit ed04845:

✅ Validation status: passed

File Status Preview URL Details
snippets/csharp/System.Linq/.editorconfig ✅Succeeded
snippets/csharp/System.Linq/Enumerable/AggregateTSource/enumerable.cs ✅Succeeded View
snippets/csharp/System.Linq/Enumerable/AggregateTSource/Enumerable.csproj ✅Succeeded
snippets/csharp/System.Linq/Enumerable/Comparers/Comparers.csproj ✅Succeeded
snippets/csharp/System.Linq/Enumerable/Comparers/CustomComparer.cs ✅Succeeded View
snippets/csharp/System.Linq/Enumerable/Comparers/EncapsulatedComparer.cs ✅Succeeded View
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/igrouping.cs ✅Succeeded View
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/IGrouping.csproj ✅Succeeded
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/ILookup.cs ✅Succeeded View
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/ILookup.csproj ✅Succeeded
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/IOrderedEnumerable.cs ✅Succeeded View
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/IOrderedEnumerable.csproj ✅Succeeded
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Linq - Copy.txt ✅Succeeded
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/lookup.cs ✅Succeeded View
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/Lookup.csproj ✅Succeeded
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Queryable/AggregateTSource/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Queryable/AggregateTSource/queryable.cs ✅Succeeded View
snippets/csharp/System.Linq/Queryable/AggregateTSource/Queryable.csproj ✅Succeeded
snippets/csharp/System.Linq/README.txt ✅Succeeded

For more details, please refer to the build report.

For any questions, please:

Copy link

Learn Build status updates of commit ca1e55a:

✅ Validation status: passed

File Status Preview URL Details
snippets/csharp/System.Linq/.editorconfig ✅Succeeded
snippets/csharp/System.Linq/Enumerable/AggregateTSource/enumerable.cs ✅Succeeded View
snippets/csharp/System.Linq/Enumerable/AggregateTSource/Enumerable.csproj ✅Succeeded
snippets/csharp/System.Linq/Enumerable/Comparers/Comparers.csproj ✅Succeeded
snippets/csharp/System.Linq/Enumerable/Comparers/CustomComparer.cs ✅Succeeded View
snippets/csharp/System.Linq/Enumerable/Comparers/EncapsulatedComparer.cs ✅Succeeded View
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/igrouping.cs ✅Succeeded View
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/IGrouping.csproj ✅Succeeded
snippets/csharp/System.Linq/IGroupingTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/ILookup.cs ✅Succeeded View
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/ILookup.csproj ✅Succeeded
snippets/csharp/System.Linq/ILookupTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/IOrderedEnumerable.cs ✅Succeeded View
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/IOrderedEnumerable.csproj ✅Succeeded
snippets/csharp/System.Linq/IOrderedEnumerableTElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Linq - Copy.txt ✅Succeeded
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/lookup.cs ✅Succeeded View
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/Lookup.csproj ✅Succeeded
snippets/csharp/System.Linq/LookupTKey,TElement/Overview/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Queryable/AggregateTSource/Project.csproj ✅Succeeded n/a (file deleted or renamed)
snippets/csharp/System.Linq/Queryable/AggregateTSource/queryable.cs ✅Succeeded View
snippets/csharp/System.Linq/Queryable/AggregateTSource/Queryable.csproj ✅Succeeded
snippets/csharp/System.Linq/README.txt ✅Succeeded

For more details, please refer to the build report.

For any questions, please:

@tarekgh tarekgh added area-System.Linq and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jul 28, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-linq

@@ -0,0 +1,269 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
Copy link
Member

Choose a reason for hiding this comment

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

I defer to the docs owners for this, but if editorconfig settings are specified shouldn't they be set repo-wide?

Copy link
Member

Choose a reason for hiding this comment

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

This file should be removed from the PR. The coding style should match the settings from the editorconfig at the root of the repo.

{
OfTypeEx1();
}
static void Main() => OfTypeEx1();
Copy link
Member

Choose a reason for hiding this comment

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

If we're embracing modern C# shouldn't this be changed to use top-level statements instead?

Copy link
Member

Choose a reason for hiding this comment

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

Yes. We'd prefer top-level statements.

We don't enforce the change on PRs, because we have many samples that pre-date top-level statements. However, if the purpose of this PR is to modernize the code, it should be top-level statements.

@@ -4,19 +4,16 @@

namespace SequenceExamples
Copy link
Member

Choose a reason for hiding this comment

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

And possibly get rid of the namespace declarations where possible.

Copy link
Member

Choose a reason for hiding this comment

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

Our style would be to use file scoped namespaces for larger samples. Using the global namespace is fine for smaller samples. (I'd call this a larger sample, personally).

@@ -109,32 +106,29 @@ public static void AllEx()
static class All2
{
// <Snippet129>
class Pet
class Pet(string name, int age)
Copy link
Member

Choose a reason for hiding this comment

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

Why specifically adopt a primary constructor in this example? The example could simply update the nullability annotation for Name or enforce immutability using init (and possibly required) instead of set if that's the end goal.

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer record or record struct types for this and other data types defined in these samples. The use (a data container) is the main scenario for record types. In the context of the sample, I don't have a strong preference for record class vs. record struct or enforcing immutability.


// Determine which people have a non-empty Pet array.
IEnumerable<string> names = from person in people
where person.Pets.Any()
where person.Pets.Length != 0
Copy link
Member

Choose a reason for hiding this comment

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

Given that these are snippets specifically intended to showcase use of Linq why not just keep Any?

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer Any here as well.

@@ -0,0 +1,61 @@

Copy link
Member

Choose a reason for hiding this comment

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

Seems to have been committed by mistake?

@@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
Copy link
Member

Choose a reason for hiding this comment

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

Curious why these project files are being deleted/renamed. If the projects are being restructured, what's the end goal here?

Copy link
Contributor Author

@DickBaker DickBaker Jul 29, 2024

Choose a reason for hiding this comment

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

the MS process mandates that there are .csproj files so that CI can validate. All good.
However it makes sense to knit *.csproj into a VS2022 .sln file so that all the analyzer improves can be applied together.
Having multiple x.csproj (same-name files e.g. program.csproj) is not acceptable to VS2022 so I renamed to unique.csproj [the *.csproj are invisible to the normal learn.microsoft.com/blah reader].

I agree with Eirik's other points of about top-level statements, NS (don't know how I missed those!)
But I lean towards analyzer recommendations [e.g. prefer .Count>0 or .Length>0 rather than Any() for perf]
having a splurge of Warnings/Messages is untidy and imho bad UX. Yes of course we are exhibiting LINQ so showing off lotsa members is good, but even good tools can be misused so "it depends" as ever.

I was not trying to sneak in contrary .editoconfig settings for long-term, but I despise "private" accessors as this is default and imho just contributes unnecessary noise to detriment of the code, i.e. I pick
dotnet_style_require_accessibility_modifiers = omit_if_default
but once I have cleaned-up such noise I am happy to revert to owner's version ["private" shouldn't re-appear]

I set the "let owner/admin modify" [sorry I can't remember exact phrase], so by all means give the changes another spin
I will watch reviewer comments with interest!

I appreciate the effort to update the doc snippets to portray latest C# semantics is a bore, but we all want such docs to give good guidance. Sometimes some ugly example throws me into digging up all the rabbit-holes [OCD!]

Anyway I am pleased the PR is now being looked at as otherwise my wasted effort crafting it!

Copy link
Member

@eiriktsarpalis eiriktsarpalis left a comment

Choose a reason for hiding this comment

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

Apart from a few nits this seems to be bringing welcome updates to the samples.

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

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

Hi @DickBaker

There are some great improvements here. But also a number of changes that should be addressed.

@@ -0,0 +1,269 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
Copy link
Member

Choose a reason for hiding this comment

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

This file should be removed from the PR. The coding style should match the settings from the editorconfig at the root of the repo.

@@ -4,19 +4,16 @@

namespace SequenceExamples
Copy link
Member

Choose a reason for hiding this comment

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

Our style would be to use file scoped namespaces for larger samples. Using the global namespace is fine for smaller samples. (I'd call this a larger sample, personally).

{
OfTypeEx1();
}
static void Main() => OfTypeEx1();
Copy link
Member

Choose a reason for hiding this comment

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

Yes. We'd prefer top-level statements.

We don't enforce the change on PRs, because we have many samples that pre-date top-level statements. However, if the purpose of this PR is to modernize the code, it should be top-level statements.


// Determine which people have a non-empty Pet array.
IEnumerable<string> names = from person in people
where person.Pets.Any()
where person.Pets.Length != 0
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer Any here as well.


double average = numbers.Average(num => long.Parse(num));
double average = numbers.Average(long.Parse);
Copy link
Member

Choose a reason for hiding this comment

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

I'm ambivalent on this change. Less experienced programmers may be confused by using a method group instead of a lambda expression. If we keep this change, we should add a note in the corresponding text to explain a method group conversion.


// Get the last item in the array, or else the default
// value for type string (null).
string last = fruits.AsQueryable().LastOrDefault();
var last = fruits.LastOrDefault();
Copy link
Member

Choose a reason for hiding this comment

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

same


Console.WriteLine(
String.IsNullOrEmpty(last) ? "[STRING IS NULL OR EMPTY]" : last);
last ?? "[STRING IS NULL OR EMPTY]");
Copy link
Member

Choose a reason for hiding this comment

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

Same comment I had on the enumerable sample. This does change the semantics of the sample.


Console.WriteLine("The last number that rounds to 50 is {0}.", last50);

// Get the last number in the array that rounds to 40.0,
// or else the default value for type double (0.0).
double last40 =
numbers.AsQueryable().LastOrDefault(n => Math.Round(n) == 40.0);
numbers
.LastOrDefault(n => Math.Round(n) == 40.0);
Copy link
Member

Choose a reason for hiding this comment

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

I'll stop commenting on it, but every sample in this file is to show a data source that uses IQueryable. The AsQueryable() calls should stay.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not a fan as adds unnecessary overhead, and imho is bad guidance
but I will revert as-was to stay friends.

Yes I also prefer the lightweight record rather than class, so I will magic that in too.


long count = fruits.AsQueryable().LongCount();
long count = fruits.LongCount(); // [CA1829, RCS1077] better to use "fruits.Length;"
Copy link
Member

Choose a reason for hiding this comment

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

I'll stop commenting on this point, but all these should be removed when the purpose is to show the given extension method:

Suggested change
long count = fruits.LongCount(); // [CA1829, RCS1077] better to use "fruits.Length;"
long count = fruits.LongCount();

@@ -109,32 +106,29 @@ public static void AllEx()
static class All2
{
// <Snippet129>
class Pet
class Pet(string name, int age)
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer record or record struct types for this and other data types defined in these samples. The use (a data container) is the main scenario for record types. In the context of the sample, I don't have a strong preference for record class vs. record struct or enforcing immutability.

@dotnet-policy-service dotnet-policy-service bot added the needs-author-action An issue or pull request that requires more info or actions from the author. label Jul 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Linq community-contribution Indicates that the PR has been added by a community member needs-author-action An issue or pull request that requires more info or actions from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants