{"id":22408347,"url":"https://github.com/phmatray/recordequality","last_synced_at":"2026-05-04T03:35:30.049Z","repository":{"id":264659231,"uuid":"894005680","full_name":"phmatray/RecordEquality","owner":"phmatray","description":"Achieve true value equality in .NET records using ValueCollection","archived":false,"fork":false,"pushed_at":"2026-04-18T11:49:59.000Z","size":37,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2026-04-18T13:26:52.791Z","etag":null,"topics":["csharp","design-patterns","dotnet","record","value-equality"],"latest_commit_sha":null,"homepage":"https://www.linkedin.com/pulse/understanding-value-equality-net-records-philippe-matray--cdlke/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phmatray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-11-25T15:30:04.000Z","updated_at":"2026-04-18T11:50:03.000Z","dependencies_parsed_at":"2024-11-25T16:46:39.351Z","dependency_job_id":null,"html_url":"https://github.com/phmatray/RecordEquality","commit_stats":null,"previous_names":["phmatray/recordequality"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/phmatray/RecordEquality","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FRecordEquality","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FRecordEquality/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FRecordEquality/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FRecordEquality/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phmatray","download_url":"https://codeload.github.com/phmatray/RecordEquality/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phmatray%2FRecordEquality/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32593945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","design-patterns","dotnet","record","value-equality"],"created_at":"2024-12-05T11:17:21.104Z","updated_at":"2026-05-04T03:35:30.023Z","avatar_url":"https://github.com/phmatray.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Value Equality in .NET Records with ValueCollection\n\nCheck my blog post on LinkedIn: [Understanding Value Equality in .NET Records](https://www.linkedin.com/pulse/understanding-value-equality-net-records-philippe-matray--cdlke/)\n\n## Overview\n\nThis project demonstrates how to achieve true value equality in .NET records, particularly when dealing with\ncollections. The default equality behavior of .NET records works well for value types and immutable properties but falls\nshort when records contain collections, which are typically compared by reference rather than by their contents.\n\nTo address this limitation, this project introduces `ValueCollection\u003cT\u003e`, a custom collection type that provides\nvalue-based equality for collections, ensuring that records containing such collections can be correctly compared by\ntheir values.\n\n## Key Concepts\n\n- **.NET Records**: Records are reference types with value-based equality in C#. They are ideal for representing\n  immutable data models.\n- **Value Equality**: The concept that two objects are considered equal if their values are the same, regardless of\n  whether they reference the same memory address.\n- **Challenges with Collections**: By default, collections like `List\u003cT\u003e` use reference equality, which can cause\n  equality checks on records to fail even if the collection contents are identical.\n- **`ValueCollection\u003cT\u003e`**: A custom collection type that overrides equality methods to ensure that collections are\n  compared based on their values, not their references.\n\n## Project Structure\n\n- **Records**: This project includes two records to demonstrate value equality: `BadOrder` and `GoodOrder`.\n    - `BadOrder`: Uses `List\u003cOrderItem\u003e` and fails equality checks due to reference-based collection comparison.\n    - `GoodOrder`: Uses `ValueCollection\u003cOrderItem\u003e` to ensure value-based comparison for the collection.\n- **`ValueCollection\u003cT\u003e` Implementation**: A custom collection class that extends `ReadOnlyCollection\u003cT\u003e` and overrides\n  the `Equals`, `GetHashCode`, and `ToString` methods to support value-based equality.\n- **Unit Tests**: Tests are included to verify the equality behavior for `BadOrder` and `GoodOrder`, as well as to\n  demonstrate the functionality of `ValueCollection\u003cT\u003e`.\n\n## Usage\n\n### Records\n\nThe project defines two primary records:\n\n- `BadOrder`:\n  ```csharp\n  public record BadOrder(string OrderId, string CustomerName, List\u003cOrderItem\u003e Items);\n  ```\n- `GoodOrder`:\n  ```csharp\n  public record GoodOrder(string OrderId, string CustomerName, ValueCollection\u003cOrderItem\u003e Items);\n  ```\n\nThe `GoodOrder` record uses `ValueCollection\u003cOrderItem\u003e` for the `Items` property, which ensures that two instances with\nidentical `Items` will be considered equal.\n\n### ValueCollection\u003cT\u003e\n\nThe `ValueCollection\u003cT\u003e` class is a custom collection type that provides value-based equality for its elements:\n\n```csharp\npublic class ValueCollection\u003cT\u003e(params IList\u003cT\u003e values)\n    : ReadOnlyCollection\u003cT\u003e(new List\u003cT\u003e(values))\n{\n    // Adds an item to the collection\n    public void Add(T item) { ... }\n\n    // Determines whether the specified object is equal to the current object\n    public override bool Equals(object? obj) { ... }\n\n    // Returns the hash code for the current object\n    public override int GetHashCode() { ... }\n\n    // Returns a string representation of the collection\n    public override string ToString() { ... }\n}\n```\n\nThe class is designed to be immutable, aside from the provided `Add` method, and ensures value-based equality checks.\n\n### Running Tests\n\nThe project includes unit tests to verify the behavior of `ValueCollection\u003cT\u003e` and the equality checks for records.\n\n- **TestBadOrderEquality**: Demonstrates the failure of equality checks for `BadOrder` due to reference equality of\n  `List\u003cT\u003e`.\n- **TestGoodOrderEquality**: Verifies that `GoodOrder` instances are correctly considered equal when they contain\n  identical items.\n- **TestValueCollection**: Tests the behavior of `ValueCollection\u003cT\u003e` for adding items, equality, and string\n  representation.\n\n## Example Code\n\n### Equality Test for GoodOrder\n\n```csharp\n[Test]\npublic void TestGoodOrderEquality()\n{\n    OrderItem item1 = new(\"ProductA\", 2, 10.0m);\n    OrderItem item2 = new(\"ProductB\", 1, 20.0m);\n\n    GoodOrder order1 = new(\"Order1\", \"John Doe\", [item1, item2]);\n    GoodOrder order2 = new(\"Order1\", \"John Doe\", [item1, item2]);\n\n    Assert.That(order1, Is.EqualTo(order2)); // This line will pass\n}\n```\n\n## Advantages of ValueCollection\u003cT\u003e\n\n- **Consistent Equality Semantics**: Ensures that two instances of a record are treated as equal if their collections\n  contain equivalent elements.\n- **Simplified Testing**: Equality behavior aligns with expectations for value-based objects, making tests easier to\n  write and maintain.\n- **Enhanced Readability**: The use of `ValueCollection\u003cT\u003e` clarifies the intent that collections should be treated as a\n  set of values rather than references.\n\n## Installation and Setup\n\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/phmatray/RecordEquality.git\n   ```\n2. Open the solution in Visual Studio or your preferred C# IDE.\n3. Build the solution to restore dependencies.\n4. Run the tests to verify the functionality of value equality in records.\n\n## Requirements\n\n- .NET 9 or later\n- NUnit 4 for testing\n\n## Conclusion\n\nThis project provides a practical approach to implementing value equality in .NET records, particularly when dealing\nwith collections. By utilizing the `ValueCollection\u003cT\u003e` class, developers can avoid common pitfalls associated with\nreference equality and ensure that their records behave in a predictable and value-consistent manner.\n\nFeel free to explore the code, experiment with different collection types, and adapt the `ValueCollection\u003cT\u003e`\nimplementation to suit your needs.\n\n## License\n\nThis project is licensed under the MIT License. See the `LICENSE` file for more details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphmatray%2Frecordequality","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphmatray%2Frecordequality","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphmatray%2Frecordequality/lists"}