[Everday C#] Add Expressions: operators article - #55469
Conversation
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
There was a problem hiding this comment.
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.mdunder 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.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- 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.
|
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. |
There was a problem hiding this comment.
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 presentsIEqualityComparer<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-usagekey.
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
Colorinheritable while its typedEqualsis 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
Coloris a reference-type class, not a C# value type, andIEquatable<Color>can't avoid boxing because reference types aren't boxed. Also, the typedEqualsmember 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 makeDictionaryorHashSetincorrect. 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#
charstores 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
decimaloperands also preserve the fractional result, so the comment incorrectly limits the choices todoubleandfloat. 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#
charis 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.
Create docs/csharp/fundamentals/expressions/operators.md covering arithmetic, unary, increment/decrement, relational, equality survey, conditional-logical, conditional (?:), simple and compound assignment.
Fixes #55334
Internal previews
Toggle expand/collapse