dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.45k stars 4.76k forks source link

[API Proposal]: `System.Guid` shall declare `IEqualityOperators<Guid, Guid, bool>` #107270

Open wsy opened 2 months ago

wsy commented 2 months ago

Background and motivation

Sometimes we want to abstract common CRUD operations to a base class and we would like to make them generic. These data classes/records will have a property named Id as primary key. When we use Guid as primary key, it won't fit in the generic constraint even though it has already overridden the '==' and '!=' operator.

API Proposal

namespace System.Collections.Generic;

public readonly struct Guid : IComparable, IComparable<Guid>, IEquatable<Guid>, IEqualityOperators<Guid, Guid, bool>, IFormattable, IParsable<Guid>, ISpanFormattable, ISpanParsable<Guid>, IUtf8SpanFormattable
{
    // These two methods have already been implemented in Guid class. No need to add more methods.
}

API Usage

// Declaration
public interface IData<TId>
{
    TId Id { get; init; }
}
public class SomeGenericClass<TData, TId> where TData : IData<TId> where TId : IEqualityOperators<TId, TId, bool>
{
    private IQueryable<TData> storage;
    public TData? GetById(TId id) => storage.SingleOrDefault(c => c.Id == id);
}

// Using
public record SomeDataClass(Guid Id) : IData<Guid>;
public class SomeConcreteClass : SomeGenericClass<SomeDataClass, Guid> { }

Alternative Designs

No response

Risks

I consider this API suggestion as "No Risks".

dotnet-policy-service[bot] commented 2 months ago

Tagging subscribers to this area: @dotnet/area-system-runtime See info in area-owners.md if you want to be subscribed.

huoyaoyuan commented 2 months ago

See also #76225. The operator interfaces have been moved to System.Numerics namespace, and implemented on numeric types only.

Additionally, GUID is more opaque and doesn't support other arithmetic operations like TimeSpan.

tannergooding commented 2 months ago

One of the key considerations is also that for things like equality or comparison, IEqualityOperators and IComparisonOperators are typically not what you want.

You instead typically want the much older and pre-existing IEquatable and IComparable interfaces.

operator == and Equals have different contracts/implementation requirements. For example, Equals has strict requirements that it follow the concept of .NET equality that is usable alongside GetHashCode and which allows it to work for collections, hash sets, and other considerations (such as guaranteeing identity equality, transitive equality, etc). While operator == has no such requirements and can basically do "anything" that is correct for the type.

This comes up in practice for things involving floating-point fields where float.NaN == float.NaN returns false but float.NaN.Equals(float.NaN) returns true.

That isn't to say that Guid couldn't implement IEqualityOperators or IComparisonOperators, but more just pointing out that even if they did you probably don't want to be using them in most scenarios.