Skip to content

[Everday C#] Add Expressions: operators article - #55469

Open
BillWagner wants to merge 11 commits into
dotnet:mainfrom
BillWagner:expressions-math
Open

[Everday C#] Add Expressions: operators article#55469
BillWagner wants to merge 11 commits into
dotnet:mainfrom
BillWagner:expressions-math

Conversation

@BillWagner

@BillWagner BillWagner commented Aug 18, 2026

Copy link
Copy Markdown
Member

Create docs/csharp/fundamentals/expressions/operators.md covering arithmetic, unary, increment/decrement, relational, equality survey, conditional-logical, conditional (?:), simple and compound assignment.

  • Add operators snippets project (net10.0, nullable, implicit usings) with 9 region-marked examples; 0 warnings, 0 errors
  • Add operators.md TOC node under Expressions and statements
  • Add reciprocal link in expressions/index.md
  • Add reciprocal link in expressions/equality.md
  • Link excluded operators (shift/bitwise, checked/unchecked) to existing Language Reference pages

Fixes #55334


Internal previews

Toggle expand/collapse
File Preview link
.openpublishing.redirection.csharp.json Preview published page
docs/csharp/fundamentals/expressions/equality.md Preview published page
docs/csharp/fundamentals/expressions/index.md Preview published page
docs/csharp/fundamentals/expressions/operators.md Preview published page
docs/csharp/fundamentals/expressions/snippets/equality/Program.cs Preview published page
docs/csharp/fundamentals/expressions/snippets/operators/Program.cs Preview published page
docs/csharp/fundamentals/object-oriented/objects.md Preview published page
docs/csharp/how-to/index.md Preview published page
docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md Preview published page
docs/csharp/language-reference/compiler-messages/record-declaration-errors.md Preview published page
docs/csharp/language-reference/operators/equality-operators.md Preview published page
docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs Preview published page
docs/csharp/toc.yml Preview published page

Create docs/csharp/fundamentals/expressions/operators.md covering
arithmetic, unary, increment/decrement, relational, equality survey,
conditional-logical, conditional (?:), simple and compound assignment.

- Add operators snippets project (net10.0, nullable, implicit usings)
  with 9 region-marked examples; 0 warnings, 0 errors
- Add operators.md TOC node under Expressions and statements
- Add reciprocal link in expressions/index.md
- Add reciprocal link in expressions/equality.md
- Link excluded operators (shift/bitwise, checked/unchecked) to
  existing Language Reference pages

Closes dotnet#55334

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
Copilot AI lite review requested due to automatic review settings August 18, 2026 15:35
@dotnetrepoman dotnetrepoman Bot added this to the August 2026 milestone Aug 18, 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 a new C# Fundamentals article that explains everyday operators (arithmetic, unary, increment/decrement, relational, equality, conditional-logical, conditional ?:, and assignment), along with runnable snippets and navigation updates so the content is discoverable from the Expressions section.

Changes:

  • Adds operators.md under Fundamentals → Expressions and links it from related Expressions articles.
  • Introduces a new snippets project (snippets/operators) with region-tagged examples used by the article.
  • Updates the C# TOC to include the new Operators article.

Reviewed changes

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

Show a summary per file
File Description
docs/csharp/toc.yml Adds the Operators node under Fundamentals → Expressions.
docs/csharp/fundamentals/expressions/operators.md New operators concept article with :::code inclusions and links to reference pages for excluded operator sets.
docs/csharp/fundamentals/expressions/snippets/operators/operators.csproj New .NET snippet project configuration for the operators examples.
docs/csharp/fundamentals/expressions/snippets/operators/Program.cs New runnable sample code containing the operator examples referenced by the article.
docs/csharp/fundamentals/expressions/index.md Adds a cross-link to the new operators article.
docs/csharp/fundamentals/expressions/equality.md Adds a cross-link to the new operators article.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/csharp/fundamentals/expressions/snippets/operators/Program.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@BillWagner BillWagner closed this Aug 18, 2026
@BillWagner BillWagner reopened this Aug 18, 2026
BillWagner and others added 8 commits August 18, 2026 14:11
- Add negative integer division example (-7/2 = -3) to clarify truncation toward zero
- Add negative-operand remainder examples (-7%3 = -1, 7%-3 = 1) with sign rule explanation
- Explain char relational comparison uses Unicode code point values (tied to grade example)
- Update != comment to explicitly say 'true when values are not equal'
- Remove invalid commented-out code from EqualityOps snippet; move === example to NOTE callout in article prose as fenced code block
- Remove nested conditional example and related prose from ConditionalOp section
- Add Console.WriteLine after each compound assignment step (hp progression: 100 -> 120 -> 110 -> 220 -> 73 -> 3)
- Rename 'Operators not covered here' to 'Other C# operators'; expand bullets with concise definitions
- Add displayName to toc.yml entry for operators.md with all covered operators

Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
…otnet#55334)

Retire the three Programming Guide equality articles and preserve their
unique content in docs/csharp/fundamentals/expressions/equality.md:

- equality-comparisons.md
- how-to-test-for-reference-equality-identity.md
- how-to-define-value-equality-for-a-type.md

Content migrated into equality.md:
- Equivalence contract (5 rules: reflexive, symmetric, transitive,
  consistent, null behavior) added to the manual-implementation section.
- New section: 'Records with reference-type members' — explains that
  synthesized record equality uses each member's own equality semantics,
  so List<T>/array members compare by reference; shows custom IEquatable
  override with SequenceEqual as the recommended fix.
- New section: 'Polymorphic equality in unsealed class hierarchies' —
  explains the compile-time dispatch hazard with IEquatable<T>, the
  GetType() guard and virtual Equals pattern for correct unsealed-class
  equality, and notes that sealed classes and records avoid the problem.

Snippet additions to snippets/equality/Program.cs:
- RecordWithCollectionProblem / RecordWithCollectionFixed regions
- PlaylistFixedDefinition type (custom IEquatable record)
- PolymorphicEqualityDefinition (Shape/Circle hierarchy with GetType() guard)
- PolymorphicEqualityUsage region

Intentionally omitted: string-interning note (per Bill's explicit decision).

Retirement wiring:
- 3 redirects added to .openpublishing.redirection.csharp.json
- TOC entries and empty parent node removed from toc.yml
- All 5 inbound links updated: objects.md, how-to/index.md,
  overloaded-operator-errors.md, record-declaration-errors.md,
  equality-operators.md
- Orphaned snippet projects deleted

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
…rators.

Implements the persisted plan from PR dotnet#55469 equality restructuring:
- Add ## Equality in class hierarchies before ## Operator overloadability
  in equality-operators.md; covers declared/runtime-type dispatch hazards,
  GetType() guard, virtual Equals, derived-class augmentation, GetHashCode
  with GetType(), sealed-class simplification, and records guidance.
- Create net10.0 snippet project at
  docs/csharp/language-reference/operators/snippets/EqualityHierarchies/
  with HierarchyShapeDefinition, HierarchyCircleDefinition, HierarchyUsage
  regions (all build-verified, 0 warnings/errors).
- Update metadata: description, ms.date, helpviewer_keywords.
- Add reciprocal Fundamentals link in new section.

Cray item 3 (relocate polymorphic equality to Language Reference) implemented.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
…ors.

Implements remaining Cray review items for PR dotnet#55469:

Cray item 1 (Records with reference-type members):
- Remove PlaylistFixed/IEquatable implementation code; replace rendered
  code blocks with brief named-strategy list; keep RecordWithCollectionProblem
  surprise example intact.

Cray item 2 (Implement equality yourself):
- Lead with code (ColorDefinition first); shorten IMPORTANT callout to
  2 sentences; consolidate member-list description into commentary after the
  code; compact equivalence contract intro; demote IEquatable<T> footnote.
- Retain IEquatableUsage region to show identity-vs-value contrast.

Cray item 3 (Polymorphic section bridge):
- Replace full polymorphic implementation in Fundamentals with a 3-sentence
  hazard summary + link to Language Reference ## Equality in class hierarchies.
- Preserve ## Polymorphic equality in unsealed class hierarchies heading.

Cray item 4 (operators.md byte-range):
- Add byte-range clarification: 'the result, 210, fits within the byte
  range of 0-255'; beginner-safe, no checked/unchecked discussion.

Cray item 5 (hash-loop): auto-resolved by PlaylistFixed removal.

Snippets:
- Remove PlaylistFixedDefinition, RecordWithCollectionFixed regions and
  PlaylistFixed type from Fundamentals Program.cs.
- Remove PolymorphicEqualityDefinition, PolymorphicEqualityUsage regions
  (Shape/Circle now live in LR EqualityHierarchies project).
- Fundamentals snippet builds 0 warnings/errors (net10.0).

Links/redirects:
- All three Programming Guide redirect targets unchanged (anchors verified).
- Add reciprocal LR link in equality.md See also.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
- Add blank line before 'Polymorphic equality in unsealed class hierarchies' section heading in equality.md
- Clarify 'declared type' with parenthetical '(the type written in the variable declaration)' in equality-operators.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c
Do a final review pass of all the changed content.
@BillWagner BillWagner changed the title Add Expressions: operators article (issue #55334) [Everday C#] Add Expressions: operators article Aug 19, 2026
@BillWagner

Copy link
Copy Markdown
Member Author

Note for reviewers: Other than the fundamentals articles, most of the changes were updating links, and moving existing content from the Programming Guide that didn't fit in fundamentals. The large blocks of changes in the language reference area have content that already existed, was in Programming guide articles that are now redundant, and was moved. Minimal style edits are the primary changes.

@BillWagner
BillWagner marked this pull request as ready for review August 19, 2026 21:02
@BillWagner
BillWagner requested a review from a team as a code owner August 19, 2026 21:02
@BillWagner
BillWagner marked this pull request as draft August 19, 2026 21:02
@BillWagner
BillWagner requested a balanced review from Copilot August 19, 2026 21:02
@BillWagner
BillWagner marked this pull request as ready for review August 19, 2026 21:04

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

Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.

Suppressed comments (17)

.openpublishing.redirection.csharp.json:5798

  • This redirect targets a fragment that no longer exists in fundamentals/expressions/equality.md, so requests to the deleted how-to page land at the top of the replacement article instead of the manual equality guidance. Point it to the section that now contains that content.
      "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality#implement-equality-yourself-when-a-type-cant-be-a-record"

.openpublishing.redirection.csharp.json:5790

  • The PR description presents this as an operators-article addition with reciprocal links, but these redirects accompany deletion of three published equality pages and their snippet projects. Document the equality-content consolidation and removals in the PR description so reviewers can assess that substantial additional scope.
    {
      "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md",
      "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality"

docs/csharp/fundamentals/expressions/equality.md:108

  • The first option is incomplete because custom record equality must use a matching GetHashCode; otherwise hash-based collections can treat equal records inconsistently. The second option incorrectly presents IEqualityComparer<T> as a collection type, and merely creating a comparer doesn't change synthesized record equality.
- **Implement `IEquatable<T>`** on the record and override `Equals` to use <xref:System.Linq.Enumerable.SequenceEqual*?displayProperty=nameWithType> for the collection members.
- **Use a collection type with value equality** — for example, a custom `IEqualityComparer<T>` or a type whose own `Equals` compares elements.

docs/csharp/language-reference/operators/equality-operators.md:4

  • This substantial AI-assisted Markdown addition lacks the required front-matter disclosure. Add the repository's ai-usage key.
ms.date: 08/19/2026

docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs:63

  • The article later says this pattern is safe and complete for a sealed class, but the sample leaves Color inheritable while its typed Equals is nonvirtual. A derived type can then introduce asymmetric equality. Seal the sample so it matches the documented contract.
class Color : IEquatable<Color>

docs/csharp/language-reference/operators/equality-operators.md:99

  • Compiler-generated record equality doesn't compare every declared property. It compares participating instance fields, including backing fields for auto-properties; a computed property without a backing field doesn't affect equality. The current wording teaches an incorrect contract.
Records handle inheritance correctly without manual work. The compiler-generated equality checks both runtime type and all declared properties, so it automatically satisfies the symmetry and transitivity requirements. Prefer `record` over a manual unsealed hierarchy when value equality is the goal.

docs/csharp/language-reference/operators/equality-operators.md:110

  • Color is a reference-type class, not a C# value type, and IEquatable<Color> can't avoid boxing because reference types aren't boxed. Also, the typed Equals member is optional, so calling all three members required contradicts the final sentence.
Here is a minimal manual implementation for a value type that can't be a record:

:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="ColorDefinition":::

The implementation provides three required members: `Equals(T?)` as the core comparison, `override Equals(object?)` for object-level calls, and `override GetHashCode()` so hash-based collections work correctly. `HashCode.Combine` is a library helper that builds one hash from the same values used by `Equals`. Implementing <xref:System.IEquatable`1> (the `Equals(T?)` overload) is optional but avoids boxing when callers already have the concrete type.

docs/csharp/language-reference/operators/equality-operators.md:124

  • The repository's Markdown guideline requires every ordered-list item to use 1. so Markdown performs the numbering. Replace the explicit sequence here.
1. **Reflexive**: `x.Equals(x)` returns `true`.
2. **Symmetric**: `x.Equals(y)` returns the same value as `y.Equals(x)`.
3. **Transitive**: if `x.Equals(y)` and `y.Equals(z)` are both `true`, then `x.Equals(z)` must be `true`.
4. **Consistent**: successive calls to `x.Equals(y)` return the same value as long as neither object changes.
5. **Null behavior**: `x.Equals(null)` returns `false`; `x.Equals(y)` must not throw when called on a non-null `x`.

docs/csharp/language-reference/operators/equality-operators.md:126

  • IEquatable<T> doesn't make dispatch follow the declared type. Overload resolution uses the declared type, but a selected virtual method still dispatches to the runtime override, which is exactly what the proposed pattern relies on. Distinguish these two mechanisms to avoid contradicting the next paragraph.
Value equality in an unsealed class hierarchy requires more care than in a sealed class to satisfy the symmetric and transitive rules. The hazard is that `IEquatable<T>.Equals(T? other)` dispatch follows the *declared type* (the type written in the variable declaration) of the variable, not its runtime type. If `Shape` declares a non-`virtual` `Equals(Shape? other)`, a variable typed as `Shape` that holds a `Circle` at runtime invokes `Shape.Equals`—silently ignoring `Circle`-specific fields. Two `Circle` objects with different radii can compare as equal when accessed through a `Shape` variable.

docs/csharp/language-reference/operators/equality-operators.md:138

  • Omitting GetType() from a hash code can't make Dictionary or HashSet incorrect. The hash contract only requires equal objects to have equal hashes; unequal runtime types can legally collide. Including the type can reduce collisions, but it isn't required for correctness.
- **`GetHashCode` includes `GetType()`**: because two objects are equal only when their runtime types match, `GetHashCode` must hash the runtime type as well as the data fields. Omitting `GetType()` here causes incorrect behavior in `Dictionary<TKey,TValue>` and `HashSet<T>`.

docs/csharp/fundamentals/expressions/operators.md:14

  • This cross-language claim is inaccurate for JavaScript: == performs coercion, ! accepts truthy and falsy operands, + can concatenate after coercion, and / doesn't perform integer division. Describe the familiar syntax without implying equivalent semantics.
> **Coming from another language?** Most operators in this article (`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`, `<`, `>`, comparison operators, and `=`) work the same as in Java, C++, and JavaScript. The main surprises for newcomers are integer division behavior, the prefix/postfix distinction for `++`/`--`, and the way compound assignment converts back to the left-hand-side type.

docs/csharp/fundamentals/expressions/operators.md:88

  • “Use == for value comparison” is unsafe as general C# guidance because == still tests identity for many reference types. Limit this advice to numeric types and strings, and tell readers that other types control their own equality semantics.
> If you're coming from JavaScript, use `==` for value comparison (C# `==` already compares by value for primitive types and strings). A common related bug is accidentally writing `=` (assignment) where you meant `==` (equality check). The compiler catches the most common forms, but double-check any `if` condition that contains `=`.

docs/csharp/fundamentals/object-oriented/objects.md:28

  • Local struct values aren't guaranteed to live on the thread stack; the JIT can keep them in registers, and structs used as fields or boxed values live elsewhere. Avoid teaching stack allocation as the reason assignment copies structs.
The thread stack allocates memory for both `p1` and `p2`. The program reclaims that memory along with the type or method in which you declare it. This memory management is one reason why structs are copied on assignment. By contrast, the common language runtime automatically reclaims (garbage collects) the memory it allocates for a class instance when all references to the object go out of scope. You can't deterministically destroy a class object like you can in C++. For more information about garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md).

docs/csharp/fundamentals/expressions/operators.md:70

  • A C# char stores one UTF-16 code unit, not necessarily a Unicode code point. Surrogate code units don't represent code points by themselves, so describe the comparison in terms of the UTF-16 code-unit value.
Relational operators work on all numeric types and `char`. For `char`, comparison uses the character's numeric Unicode code point value, not any alphabetical or domain-specific ordering. In the grade example above, `'B'` is greater than or equal to `'A'` because `'B'` has Unicode value 66 and `'A'` has Unicode value 65 — the *numbers* determine the comparison, not the meaning of the letter grades.

docs/csharp/fundamentals/expressions/snippets/operators/Program.cs:19

  • decimal operands also preserve the fractional result, so the comment incorrectly limits the choices to double and float. Include all three built-in fractional numeric types.
// To get a decimal result, at least one operand must be a double or float

docs/csharp/fundamentals/expressions/snippets/operators/Program.cs:73

  • A C# char is a UTF-16 code unit and can contain a surrogate value that isn't a Unicode code point. Use the precise term in the published sample comments too.
// char comparison uses the character's numeric Unicode code point, not alphabetical position
// 'B' (U+0042, value 66) is less than 'A' (U+0041, value 65)? No — 'A' (65) < 'B' (66)

docs/csharp/fundamentals/object-oriented/objects.md:42

  • “Records are reference types” excludes record struct, which is a value type. Distinguish record classes from record structs so the equality guidance doesn't contradict the type system.
  The default <xref:System.ValueType?displayProperty=nameWithType> implementation of `Equals` uses boxing and reflection in some cases. For information about how to provide an efficient equality algorithm that's specific to your type, see [Implement equality yourself when a type can't be a record](../../language-reference/operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record). Records are reference types that use value semantics for equality.

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#: Arithmetic, comparison, logical, conditional, and assignment operators

2 participants