Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which solution is the closes to type-specific implementation of generic class?

Tags:

c#

generics

C++ has a feature I'd like to use in C#, namely type-specific implementation for generic class. What I mean is that I can define a generic class, but provide an exception for specific generic type.

I'm writing a text comparison tool. I've already implemented comparing two files by lines and now I want to implement comparing them by characters. The algorithm is exactly the same - except the compared type. In the first case I'm doing a safe-hashing of text lines into ints (safe-hashing in terms that different lines always have different hashes), in the second case I'd like to use chars.

The naïve solution is to cast chars to ints, but in the worst case it will quadruple amount of memory needed to keep all entries. I could simply hold objects, but applying == to them might cause boxing, which will result in significant performance drops. So I turned to generics.

public class ComparisonContext<TData> where TData : struct
{
    public TData[] DataA { get; }
    public TData[] DataB { get; }
}

// (...)

if (context.DataA[x].Equals(context.DataB[y])) ...

I'm unsure though if it still won't cause boxing. One solution I thought about is requiring that TData implements IEquatable<TData>.

public class ComparisonContext<TData> where TData : struct, IEquatable<TData>
{
    public TData[] DataA { get; }
    public TData[] DataB { get; }
}

// (...)

if (((IEquatable<TData>)context.DataA[x]).Equals(context.DataB[y])) ...

The best solution would be simply to implement a comparison method inside the context class, specific to char and int types. But in C# this is not possible.

Is there any better solution in terms of performance and readability than what I came up with?

like image 792
Spook Avatar asked Sep 16 '25 05:09

Spook


1 Answers

to implement a comparison method inside the context class, specific to char and int types. But in C# this is not possible.

If I understand correctly, you just want extension methods on ComparisonContext<int> and ComparisonContext<char>?

public static class Extensions {
    public static bool DataAreEqual(this ComparisonContext<int> context, int dataAIndex, int dataBIndex)
        => context.DataA[dataAIndex] == context.DataB[dataBIndex];
    public static bool DataAreEqual(this ComparisonContext<char> context, int dataAIndex, int dataBIndex)
        => context.DataA[dataAIndex] == context.DataB[dataBIndex];
}

Usage:

context.DataAreEqual(x, y);
// instead of
// ((IEquatable<TData>)context.DataA[x]).Equals(context.DataB[y])

Boxing does indeed occur in your first approach, if the context that you are using is a ComparisonContext<TData>. Since TData is only constrained to value types, so calling Equals on that will call Equals(object). If context is actually a ComparisonContext<int>, where the type argument is a type that has their own Equals that takes their own type, then there is no boxing.

Boxing also occurs in your second approach, but that's only because you casted to IEquatable<T>, which is unnecessary.

No boxing occurs if you wrote where TData : struct, IEquatable<TData> and just did:

context.DataA[x].Equals(context.DataB[y])

If you are on .NET 7+, you can also constrain TData to IEqualityOperators<TData, TData, bool>.Then you can just compare them directly with ==, which I think is a bit more readable. This also does not box.

like image 138
Sweeper Avatar answered Sep 19 '25 10:09

Sweeper