6

I really like the C# 9 records. However, I can't find an elegant and concise way to accomplish somethink like this:

record MyKey(string Foo, int Bar);

[Fact]
public void ShouldEqualIgnoreCase()
{
    MyKey a = new("ID", 42);
    MyKey b = new("Id", 42);

    Assert.Equal(a, b);
}
DavWEB
  • 420
  • 4
  • 14
  • 3
    Does this answer your question? [Custom Equality check for C# 9 records](https://stackoverflow.com/questions/64326511/custom-equality-check-for-c-sharp-9-records) – Eldar Jun 18 '21 at 12:43
  • You could apply `.ToUpper()` to both strings before checking equality. – Felix Castor Jun 18 '21 at 12:45
  • @FelixCastor I wan't to preserve the casing of the strings, so I cannot apply `.ToUpper()` before assigning them to the record. – DavWEB Jun 18 '21 at 12:54
  • 1
    @Eldar your link is helpful, thanks. However I'm not really satisfied with the result, having to implement Equals (and GetHashCode) myself again. Hence looking for a nice solution (maybe there is none). – DavWEB Jun 18 '21 at 12:57
  • As for the duplicate vote, don't know if it makes a difference to the other question, but I do only care about case-insensitivity and no further logic or exceptions. – DavWEB Jun 18 '21 at 12:59
  • @DavWEB You have to implement the comparison and/or equality to make it case-insensitive, otherwise there is no workaround, no built-in functionality for such a thing, and there never will be, as I know, and as for any type, class, struct or record. Additional note: except the @.JonasH's proposal below, which displaces the problem. –  Jun 18 '21 at 12:59
  • 5
    Have you considered making a "CaseInsensitiveString"-type? That could let you override the equality comparison in one place instead of every record. – JonasH Jun 18 '21 at 13:01
  • If it would be somehow possible to exclude certain properties from equality comparison, this would help. I could add `Foo` and `FooAsUpper` ignoring the `Foo` property in the comparison. – DavWEB Jun 18 '21 at 13:10
  • `Assert.True(a.Foo.Equals(b.Foo, StringComparison.OrdinalIgnoreCase));` you can do like this – akhilv Oct 10 '22 at 15:45

1 Answers1

2

Just use ValueObjects instead of primitives. Just as JonasH already said.

Make a new class named "CaseInsensitiveString" that implements IEquatable. The record will then use the Equals method.

Something like this:

    public readonly struct CaseInsensitiveString : IEquatable<CaseInsensitiveString>
    {
        public readonly String StringValue;

        public CaseInsensitiveString( string value )
        {
            StringValue = value;
        }

        public bool Equals( CaseInsensitiveString other)
        {
            return string.Equals( StringValue, other.StringValue, StringComparison.InvariantCultureIgnoreCase );
        }

        public static implicit operator string( CaseInsensitiveString h ) => h.StringValue;
        public static implicit operator CaseInsensitiveString( string s ) => new CaseInsensitiveString(s);
    }

Because of the implicit operators you can just use strings:

record MyKey(CaseInsensitiveString Foo, int Bar);
...
var a = new MyKey("ID", 42 );

See also: https://wiki.c2.com/?PrimitiveObsession`

T1Space
  • 71
  • 1
  • 8
  • 1
    The C2 articles on `ValueObject` are just Java people re-inventing (if not re-discovering...) [refinement-types](https://en.wikipedia.org/wiki/Refinement_type) from FP... - Anyway, in my experience I find that refinement-types should be `readonly struct` to minimize their overhead (as they don't/shouldn't have any mutable state of their own). In C# 11 we can now use `readonly record struct` too. – Dai Nov 11 '22 at 15:10