哪个解决方案是最接近于泛型类的类型特定实现?

huangapple go评论68阅读模式
英文:

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

问题

C++有一个功能我想在C#中使用,即为泛型类实现特定类型的实现。我的意思是,我可以定义一个泛型类,但为特定泛型类型提供一个例外。

我正在编写一个文本比较工具。我已经实现了按行比较两个文件,现在我想按字符比较它们。算法完全相同,只是比较的类型不同。在第一种情况下,我正在对文本行进行安全哈希处理,将它们转换为int(在这里的安全哈希处理指的是不同的行总是具有不同的哈希值),在第二种情况下,我想使用char

最简单的解决方案是将字符转换为整数,但在最坏的情况下,会使需要保存所有条目的内存量增加四倍。我可以简单地保存对象,但对它们应用==运算符可能会导致装箱,这将导致性能显著下降。因此,我转向了泛型。

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

// (...)

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

不过,我不确定这是否仍然会导致装箱。我考虑过的一个解决方案是要求TData实现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])) ...

最佳解决方案只是在上下文类中实现一个特定于charint类型的比较方法。但在C#中,这是不可能的。

在性能和可读性方面,是否有比我想出的更好的解决方案?

英文:

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?

答案1

得分: 2

以下是翻译好的内容:

在上下文类内部实现一个比较方法,专门针对 charint 类型。但在 C# 中,这是不可能的。

如果我理解正确,您只希望在 ComparisonContext<int>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];
}

用法:

context.DataAreEqual(x, y);
// 而不是
// ((IEquatable<TData>)context.DataA[x]).Equals(context.DataB[y])

在您的第一种方法中确实会发生装箱,如果您正在使用的 contextComparisonContext<TData>。由于 TData 仅限于值类型,因此在其上调用 Equals 将调用 Equals(object)。如果 context 实际上是 ComparisonContext<int>,其中类型参数是具有自己的接受自己类型的 Equals 的类型,那么就不会发生装箱。

在您的第二种方法中也会发生装箱,但这只是因为您进行了到 IEquatable<T> 的强制转换,这是不必要的。

如果您编写了 where TData : struct, IEquatable<TData> 并且只是这样写:

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

如果您使用的是 .NET 7+,您还可以将 TData 限制为 IEqualityOperators<TData, TData, bool>。然后,您可以直接使用 == 进行比较,我认为这样更可读。这也不会装箱。

英文:

> 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])

<hr>

Boxing does indeed occur in your first approach, if the context that you are using is a ComparisonContext&lt;TData&gt;. Since TData is only constrained to value types, so calling Equals on that will call Equals(object). If context is actually a ComparisonContext&lt;int&gt;, 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&lt;T&gt;, which is unnecessary.

No boxing occurs if you wrote where TData : struct, IEquatable&lt;TData&gt; and just did:

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

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

huangapple
  • 本文由 发表于 2023年6月29日 16:37:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76579399.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定