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 int
s (safe-hashing in terms that different lines always have different hashes), in the second case I'd like to use char
s.
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?
to implement a comparison method inside the context class, specific to
char
andint
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With