C#扩展方法编译/兼容性检查失败,基于命名空间的顺序

5
我正在编写两个扩展方法,一个用于单个对象,另一个用于对象集合。当调用这些扩展方法时,C#编译器似乎会混淆使用哪一个,并且导致编译失败。
更令人惊讶的是,如果我将这些扩展方法移动到不同的命名空间中,即使在调用点包含这两个命名空间,只有当命名空间按字母顺序排序时,编译才会失败 - 切换命名空间会导致编译成功。
以下是代码:
public static class DBObjectExtensions
{
    public static void PopulateRelations<T>(this T obj, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
    {
        if (obj == null)
        {
            return;
        }

        obj.Transaction.PopulateRelations<T>(new[]{ obj }, relationsToPrefetch);
    }

    public static void PopulateRelations<T>(this IEnumerable<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
    {
        var first = objects.FirstOrDefault();
        if (first == null)
        {
            return;
        }

        first.Transaction.PopulateRelations<T>(objects, relationsToPrefetch);
    }
}

这是导致编译失败的调用站点代码行:

List<ITable> list = ... // ITable inherits from IDBObject
list.PopulateRelations(xxx);

出现错误CS0311:

类型'System.Collections.Generic.List'无法用作泛型类型或方法'Granta.MI.DBObjectExtensions.PopulateRelations(T, params Granta.MI.RelationToPrefetch[])'中的类型参数'T'。从'System.Collections.Generic.List'到'Granta.MI.IDBObject'不存在隐式引用转换。

请注意,如果我删除第二个扩展方法,则此行编译成功。

还要注意编写跳板方法(对于每种可能的集合类型...)也可以正常工作:

public static void PopulateRelations<T>(this List<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
{
    ((IEnumerable<T>)objects).PopulateRelations(relationsToPrefetch);
}

public static void PopulateRelations<T>(this IList<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
{
    ((IEnumerable<T>)objects).PopulateRelations(relationsToPrefetch);
}

为什么编译器无法找到匹配的扩展方法?更令人困惑的是,如果我将其中一个方法放在不同的命名空间中,并包含该命名空间,那么为什么编译就会成功呢?有没有什么办法可以解决这个问题呢?


重载决议不考虑约束条件。此外,在方法函数内部似乎不需要使用T,为什么您需要在扩展中使用T而不是只使用IDbObject - Jcl
@Jcl 人们经常会对 T 的使用感到困惑,就像你所说的,如果在扩展方法中不需要 T,只需像 @jakub-lortz 的回答中那样简单地更改方法即可。 - mijail
抱歉 - 我在回答中没有包含不这样做的原因。原因是 Transaction.PopulateRelations 也需要一个类型 T,然后调用 typeof,所以我需要那个 T 是一个具体的类型,而不仅仅是 IDBObject - Michael Parker
2个回答

2

通用约束不是方法签名的一部分,所以编译器选择了PopulateRelations<T>(this T obj, params RelationToPrefetch[] relationsToPrefetch),因为TIEnumerable<T>更具体。

例如,在这两个方法之间:

public static void PopulateRelations(this List<ITable> obj, params RelationToPrefetch[] relationsToPrefetch)
{
    // Do something
}

public static void PopulateRelations(this IEnumerable<ITable> objects, params RelationToPrefetch[] relationsToPrefetch)
{
    // Do something
}

第一个选项是在调用时选择的:
List<ITable> list;
PopulateRelations(list, something); // Not calling as extension method to more clear

因为listList<ITable> 直接匹配


Eric Lippert写了一篇与此相关的有用博客文章:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx - Dave Zych
那么,当我重新排列命名空间时,为什么编译成功了呢? - Michael Parker
@MichaelParker 你如何对命名空间进行排序? - Matteo Umili
如果方法A与调用代码在同一命名空间中,而方法B位于不同的命名空间中,则编译将失败。如果我将这两个方法交换位置,使得方法A现在位于不同的命名空间中,而第二个方法现在与调用代码在同一命名空间中,则编译将成功。 - Michael Parker
当您将方法A移动到另一个命名空间时,是否需要使用using ThatNamespaceName; - Matteo Umili

0

你的泛型类型被限制为IDBObject,因此你可以将扩展方法设置为非泛型:

public static void PopulateRelations(this IDBObject obj, params RelationToPrefetch[] relationsToPrefetch)
{
    //...
}

public static void PopulateRelations(this IEnumerable<IDBObject> objects, params RelationToPrefetch[] relationsToPrefetch)
{
    //...
}

它解决了编译错误。


不完全是这样,因为它们正在委托给事务上的一个通用方法 PopulateRelations,该方法使用 T 并在 T 上调用 typeof。我意识到我在原帖中没有包含这个 - 抱歉。 - Michael Parker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接