Skip to content

[API Proposal]: IBitwiseEquatable<T> #75642

Open
@stephentoub

Description

@stephentoub

Background and motivation

Most of our vectorized implementations on MemoryExtensions check RuntimeHelpers.IsBitwiseEquatable to determine whether vectorization is feasible. Today, IsBitwiseEquatable is hardcoded to a finite list of primitive types; #75640 extends that further, but overriding Equals / implementing IEquatable<T> opts you out again. Developers can still get the vectorization benefits of these implementations, but it's awkward and unsafe, e.g. with a type like:

struct MyColor : IEquatable<T> { ... }

instead of:

ReadOnlySpan<MyColor> colors = ...;
MyColor value = ...;
return colors.IndexOf(value);

it's something more like:

ReadOnlySpan<MyColor> colors = ...;
MyColor value = ...;
return MemoryMarshal.Cast<MyColor, int>(colors).IndexOf(Unsafe.As<MyColor, int>(ref value));

and most importantly, it's something someone has to write code for on each use rather than it "just work"ing.

If we instead expose a way for a type to be annotated as "You can trust that my Equals override and/or IEquatable implementation are identical to bitwise equality semantics", then we can special-case IsBitwiseEquatable to recognize this annotation, and everything else just lights up.

API Proposal

namespace System;

public interface IBitwiseEquatable<T> : IEquatable<T>
{
    // no additional methods, it's just a marker interface
}

API Usage

struct MyColor : IBitwiseEquatable<MyColor>
{
    private byte R, G, B, A;

    public bool Equals(MyColor other) => R == other.R && G == other.G && B == other.B && A == other.A;

    public override bool Equals(object obj) => obj is MyColor c && Equals(c);

    public override int GetHashCode() => HashCode.Combine(R, G, B, A);
}

Alternative Designs

It could also be an attribute, e.g.

namespace System;

[AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class BitwiseEquatableAttribute : Attribute
{
    public BitwiseEquatableAttribute() { }
}

that could then be applied to the type, e.g.

[BitwiseEquatable]
struct MyColor : IEquatable<MyColor>
{
    private byte R, G, B, A;

    public bool Equals(MyColor other) => R == other.R && G == other.G && B == other.B && A == other.A;

    public override bool Equals(object obj) => obj is MyColor c && Equals(c);

    public override int GetHashCode() => HashCode.Combine(R, G, B, A);
}

Risks

It's yet one more thing a developer implementing equality would need to think about. We'd probably want the C# compiler to emit it in some situations for records, and anything we do around #48733 should also factor it in. We might also want analyzers that try to flag types which implement IBitwiseEquality but don't integrate all fields into its Equals, don't do a simple field-by-field comparison or unsafe-cast-memcmp in their Equals, etc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Memory

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions