具有不满意类型约束的通用方法是隐藏扩展方法

时间:2018-05-16 22:54:24

标签: c# generics extension-methods

我正在使用一个简洁的小HashCode实用程序,其中包含一些有助于快速生成哈希值的实例方法。我已经简化了这个问题,但基本上就是这样:

public struct HashCode
{
    private readonly int _hashCode;
    public HashCode(int hashCode) { _hashCode = hashCode; }
    override int GetHashCode() => _hashCode;

    public static HashCode Start => new HashCode(17);    
    public HashCode Hash(int integer) => new HashCode(unchecked(integer + hashCode * 31));    
    public HashCode Hash<T>(T value) where T : struct => Hash(value.GetHashCode());    
    public HashCode Hash(string value) => Hash(value?.GetHashCode() ?? 17);
    public HashCode Hash<T>(T? nullable) where T : struct =>
        Hash(nullable?.GetHashCode() ?? 17);
    // Other reference types, user must explicitly specify a comparer
    public HashCode Hash<T>(T obj, IEqualityComparer<T> comparer) =>
        Hash(obj == null ? 17: comparer.GetHashCode(obj));  

    public static implicit operator int(HashCode hashCode) => hashCode.GetHashCode();
}

允许光滑的哈希实现,如:

class Person {
    // ...
    override GetHashCode() => HashCode.Start.Hash(name).Hash(age).Hash(...);
}

正如您所看到的,它可以避免装箱等。如果您直接对参考类型进行哈希处理,则必须指定一个比较器以确保您知道它是如何被哈希处理的。似乎合理

现在,我希望在我的一个项目中添加一些扩展方法(即而不复制和修改库),以便我可以轻松添加一些我自己经常使用的consice哈希函数类型,并使用完全相同的语法使用它们:

public static class HashCode_Extensions
{
    public static HashCode Hash(this HashCode hc, DateRange range) =>
        hc.Hash(range?.begin).Hash(range?.end);

    public static HashCode Hash(this HashCode hc, IEnumerable<T> list) where T : struct =>
        list.Aggregate(hc, (hc, elem) => hc.Hash(elem));

    // etc...
}

我以为我太聪明了,大量的复制粘贴代码就会消失。

class Meeting {
    // ...
    override GetHashCode() => HashCode.Start.Hash(name).Hash(dateRange).Hash(invitees);
}

不幸的是,编译器正在选择我自己的泛型实例方法,即使我的方法是完美的,而泛型方法的类型约束不是

  

CS0453“DateRange”类型必须是非可空值类型才能在泛型类型的方法'HashCode.Hash(T)'

中将其用作参数'T'

显然,编译器已经确定Hash的“最佳重载”是具有非满足泛型类型约束的那个。太糟糕了,因为我的扩展方法非常适合。

有没有什么方法可以欺骗编译器使用正确的方法而不必在调用Hash时使用不同的函数名称或包含'dummy'参数?

如果我有一些向后兼容的方式来更改允许我的扩展方法被注意到的HashCode库,我也会很高兴。 (我不想将自定义重载添加到基本库中,因为这些自定义类型当前不存在于其名称空间中)

1 个答案:

答案 0 :(得分:0)

一个小技巧将起作用。创建一个这样的类:

public class RequireStruct<T> where T : struct { }

将其作为可选参数放在通用方法中,如下所示:

public struct HashCode
{
    private readonly int _hashCode;
    public HashCode Hash<T>(T value, RequireStruct<T> ignore = null) where T : struct => Hash(value.GetHashCode());

}

现在当你这样做时,它会选择你想要的扩展方法:

HashCode code = new HashCode();
code.Hash(new DateRange());

here被盗。