-
Notifications
You must be signed in to change notification settings - Fork 4.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API Proposal: StringComparer.IsWellKnownComparer #50059
Comments
The compares override |
Ah, ok - |
Edit: Somehow I edited your original comment instead of posting my own response. Brilliant. 😑 Edit x2: I think my wording "this comparer is indistinguishable from" is incorrect. It should really be thought of more along the lines of "if you need to instantiate a collection that replicates this behavior, I'll help you discover which |
namespace System
{
public abstract class StringComparer
{
public static bool IsWellKnownOrdinalComparer(IEqualityComparer<string?>? comparer,
out bool ignoreCase);
public static bool IsWellKnownCultureAwareComparer(IEqualityComparer<string?>? comparer,
[NotNullWhen(true)] out CompareInfo? compareInfo,
out CompareOptions compareOptions);
}
} |
Scenario
When serializing
Dictionary<string, ...>
,HashSet<string>
, and similar collection types, the serializer may need to know what comparer is in use so that it can include that information in the payload. This is normally provided byISerializable.GetObjectData
, but that interface is intended for serializers that embed full type information inside the payload - a practice we strongly discourage.Even though APIs like
Dictionary<,>.Comparer
andHashSet<>.Comparer
are public, there's no good way to inspect the returned instance and see what comparison is being used under the covers. This API proposal provides a way to perform this inspection without relying onBinaryFormatter
or related infrastructure.Proposed API
Usage
Behavior and discussion
Between the returned ignoreCase, compareInfo, and compareOptions values, enough information is provided to reconstruct an equivalent
StringComparer
instance. It is possible that both of theIsWellKnownComparer
APIs will return false; e.g., if a completely custom comparer is in use, or if a new comparer type is added in a future release. Callers must be resilient against these possibilities and should implement a graceful fallback mechanism.It is possible for a call to
IsWellKnownOrdinalComparer
to return true, even if the instance being queried isn't the exact same object reference as eitherStringComparer.Ordinal
orStringComparer.OrdinalIgnoreCase
. For example, the singletonEqualityComparer<string>.Default
is a well-known ordinal comparer, even though it is not an instance ofStringComparer
. There are additionally some edge cases (primarily involvingBinaryFormatter
orDataContractSerializer
) where the runtime will instantiate a new ordinalStringComparer
rather than use the singletonStringComparer.Ordinal
. The proposedIsWellKnownOrdinalComparer
method only describes whether the queried instance's behavior is indistinguishable from one of those built-in singleton instances.Technically,
EqualityComparer<string>.Default
is still distinguishable fromStringComparer.Ordinal
, asEqualityComparer<string?>.Default.GetHashCode((string?)null)
will return 0, andStringComparer.Ordinal.GetHashCode((string?)null)
will throw an exception. However, the typical use case forGetHashCode(T)
is to generate a hash code for a non-null key for insertion into a bucketed collection. So it's good enough for our purposes and we'll just treat them as indistinguishable to make life easier for our callers.The
IsWellKnownOrdinalComparer
method will return false when given null,EqualityComparer<object>.Default
, or similar, as these are object comparers, not string comparers. It also avoids the slippery slope problem of trying to special-case allowingEqualityComparer<TInterface>.Default
for anyTInterface
thatstring
implements.Alternative designs
I had considered simply returning an instance of the
StringComparison
enum from this API, but this will not work given Orleans's core scenario of serialization. The linguistic enum values ofStringComparison
are dependent on the ambient environment (CultureInfo.CurrentCulture
), so there's no guarantee that two different machines will see the same behavior when instantiating a collection around a culture-aware comparison.In an ideal world we'd be able to use discriminated unions (see dotnet/csharplang#113), and the API could be defined as below.
In the absence of this language feature, the cleanest design seemed to be splitting the two queries "are you ordinal?" and "are you linguistic?" across two separate methods.
Finally, we could consider making these new APIs instance methods on
StringComparer
, as shown below.The upside to this is that it doesn't introduce a static API, which usability studies have shown are difficult for consumers to use. However, it doesn't allow passing
EqualityComparer<string>.Default
, whose return value is not in theStringComparer
type hierarchy. This means that there would be no easy way to retrieve the comparer behavior fromnew Dictionary<string, ...>().Comparer
.Finally, we could consider making the entire type hierarchy public and requiring callers to check the types themselves. However, for various reasons including performance and compatibility, this would involve making the following types public: OrdinalComparer, CultureAwareComparer, OrdinalCaseSensitiveComparer, OrdinalIgnoreCaseComparer, NonRandomizedStringEqualityComparer, and GenericEqualityComparer<T>; and it would require us to add properties to each of these types to allow for caller inspection. This would significantly increase our API surface and complicate the scenario. Using helper APIs hanging off of
StringComparison
allows us to paper over this complexity and present a simpler story for our users.>> Marked partner blocking (/cc @ReubenBond)
The text was updated successfully, but these errors were encountered: