扩展方法冲突

52

假设我有两个字符串的扩展方法,在两个不同的命名空间中:

namespace test1
{
    public static class MyExtensions
    {
        public static int TestMethod(this String str)
        {
            return 1;
        }
    } 
}

namespace test2
{
    public static class MyExtensions2
    {
        public static int TestMethod(this String str)
        {
            return 2;
        }
    } 
}

这些方法仅为示例,它们并没有实际作用。

现在让我们考虑这段代码:

using System;
using test1;
using test2;

namespace blah {
    public static class Blah {
        public Blah() {
        string a = "test";
        int i = a.TestMethod(); //Which one is chosen ?
        }
    }
}

问题:

我知道只有一个扩展方法会被选择。
它将是哪一个?为什么?

编辑:

这也让我困扰,但毕竟这是静态类中的静态方法:

如何从特定命名空间中选择某个方法?
通常我会使用Namespace.ClassNAME.Method()... 但那样就打败了扩展方法的初衷。而且我不认为你可以使用Variable.Namespace.Method()


5
如果你有疑问,可以称呼 test1.MyExtensions.TestMethod(a) 而不是 a.TestMethod()。请注意,这并不是对原代码的修改,而是提供了一个备选方案。 - k3b
我不知道,但我认为这本不应该发生...在类中给方法一个更具描述性的名称,而不是试图强制编译器自动选择一个。 我很想看看回复。 - Pieter Germishuys
2
我不是说我会这样做。但是假设你正在使用某个库,而不知道的是,那里有一个同名的扩展方法...只是想知道它是如何被选择的。 - Yochai Timmer
2
非常好的问题,我认为 - 不管其他建议如何,这都是值得知道的。 - Grant Thomas
test1.MyExtensions.TestMethod(a) 打破了扩展方法的思想。我已经编辑了我的问题,那不是真正困扰我的事情。 - Yochai Timmer
发现这种方法更加简洁,使用using static来指定您想要使用的扩展类 - https://riptutorial.com/csharp/example/34/explicitly-using-an-extension-method - ProfNandaa
5个回答

34

没有方法被选择:调用是模棱两可并且无法编译。

为什么不能使用 Namespace.ClassNAME.Method()?当然,你可以像普通静态方法一样处理扩展方法,实际上这是你解决不明确性并使程序编译的唯一方法。


好的,但是有没有关于编译器会选择哪一个以及为什么的描述?我还在想我不能做Variable.namespace.Method()。 - Yochai Timmer
编译器不会选择任何一个,你的代码将无法编译。 - Femaref
2
@Yochai: 编译器不会选择,因为如果两个扩展方法都可见,它将拒绝编译。正如你所说的那样,variable.Namespace.Method()在语法上是无效的,因此也不会编译。 - Jon

34
我有完全相同的疑问,所以我在两年后找到了这篇文章。但是,我认为需要注意的是,如果调用重复扩展方法的代码不在它们之一的命名空间中,那么只会出现"调用不明确"错误。 如果OP将类Blah的命名空间更改为test1test2,则代码将编译,并且与调用者在同一命名空间中的扩展将被使用 - 即使两个命名空间都包含在usings中。因此,如果Blahtest1命名空间中,则返回"1",如果Blahtest2命名空间中,则返回"2"。
我认为这很重要,因为我认为主流用例之一是在本地类库中具有扩展名的共享实用程序库的引用(例如,开发人员共享常用实用程序库,但可能无意中具有相同的本地自定义扩展)。通过将自定义本地扩展保持在使用它们的代码相同的命名空间中,您可以保持扩展调用语法,而不必退回到将它们视为静态方法调用的方式。

1
非常重要的一点。这实际上解决了我一直在与之斗争的痛苦冲突。谢谢! - Kris McGinnes
2
注意,命名空间是分层的,因此如果您想在整个项目中覆盖某个第三方库的扩展,可以将自己的扩展放在项目使用的顶级命名空间中。 - Alex
很好的评论。在看到这个之前,我一直被“此代码无法编译”的评论困扰着。我的代码肯定可以编译,并且这解释了为什么首选选择了特定的方法。对于我的特定情况来说,它非常完美,因为我需要覆盖其他扩展方法的逻辑。 - DarinH

13

正如Jon所说,如果在编译时这两个都存在,编译将会失败。

但是如果只有一个存在于编译时,而外部库稍后更新以添加第二个,则您编译的代码仍将继续使用第一个。这是因为编译器内部会将您的代码转换为调用namespace.classname.method的长格式。


4

我将大型解决方案从.Net 4.7.1迁移到了.Net 4.7.2。我们在代码中使用LINQ,并使用名为MoreLinq的知名和成熟的库https://www.nuget.org/packages/morelinq/

.Net 4.7.1没有.ToHashSet()方法。我们使用MoreLinq库中的.ToHashSet()。在同一类的同一cs文件中,我们同时使用using System.Linq;using MoreLinq;

我将一个项目重新定位到了.Net 4.7.2,编译器显示了如上所述的The call is ambiguous错误。原因是.Net 4.7.2添加了具有相同名称.ToHashSet()的新扩展方法。

我无法重新实现巨大的代码库。我不能用另一个库替换MoreLinq。这就是我所做的。我在一个新文件中创建了一个新类,在其中我使用了using System.Linq;,但没有使用using MoreLinq;。这是文件(ToHashsetHelpers.cs):

using System.Collections.Generic;
using System.Linq;

namespace Common.Helpers
{
    /// <summary>
    /// This class with only one method helps to resolve
    /// name conflict between .Net 4.7.2 and MoreLinq libraries.
    ///
    /// .Net 4.7.2 introduced a new extension method named '.ToHashSet()'.
    /// But MoreLinq already has the same method.
    ///
    /// After migrating our solution from .Net 4.7.1 to 4.7.2
    /// C# compiler shows "The call is ambiguous" error.
    ///
    /// We cannot have both "using System.Linq;" and "using MoreLinq;" in the same C# file that
    /// uses '.ToHashSet()'.
    ///
    /// The solution is to have method with different name in a file like this.
    /// </summary>
    public static class ToHashsetHelpers
    {
        /// <summary>
        /// The name of this method is ToHashset (not ToHashSet)
        /// </summary>
        public static HashSet<TSource> ToHashset<TSource>(this IEnumerable<TSource> source)
        {
            // Calling System.Linq.Enumerable.ToHashSet()
            return source.ToHashSet();
        }
    }
}

我将整个解决方案中所有的.ToHashSet()重命名为.ToHashset()


0

我对同样的问题感到疑惑,所以在一个 asp.net core 6 项目中进行了快速测试。

如果你尝试这样做,它将无法编译。与其他涉及扩展方法的不明确调用或语句类似。

using TestExtNs;
using TestExtNs2;

namespace YourBlazorProject.Server
{
    public class TestMe
    {
        public void Test() { }
    }
}

namespace TestNs
{
    public static class Tester
    {
        public static void RunTest() // Exec this
        {
            var x = new YourBlazorProject.Server.TestMe();
            x.Test();
            x.TestExt(); // does not compile !!! error CS0121

            TestExtNs.TesterExt.TestExt(x); //explicit call as working alternative
        }
    }
}

namespace TestExtNs
{
    public static class TesterExt
    {
        public static void TestExt(this YourBlazorProject.Server.TestMe y)
        {
            Console.WriteLine("ExtNs");
            System.Diagnostics.Debug.WriteLine("#> DIAG: ExtNs");
        }
    }
}

namespace TestExtNs2
{
    public static class TesterExt
    {
        public static void TestExt(this YourBlazorProject.Server.TestMe y)
        {
            Console.WriteLine("ExtNs2");
            System.Diagnostics.Debug.WriteLine("#> DIAG: ExtNs2");
        }
    }
}

替代方案:如果在同一命名空间中存在扩展方法,则使用该“更接近”的方法;否则将无法编译。

// SomeTest.cs (example for 'closer namespace')

using TestExtNs; // This is hard REQUIREMENT for bringing the extension method from TestExtNs into scope !!!

namespace YourBlazorProject.Server
{
    public class TestMe
    {
        public void Test() { }
    }
}

namespace TestNs
{
    public static class Tester
    {
        public static void RunTest() // Exec this
        {
            var x = new YourBlazorProject.Server.TestMe();
            x.Test();

            x.TestExt(); //Ns
            TestExt(x); //Ns
            TestExtNs.TesterExt.TestExt(x); //ExtNs
        }

        public static void TestExt(this YourBlazorProject.Server.TestMe y)
        {
            Console.WriteLine("Ns"); //writes to the Console Window of the *.Server.exe if its out-of-process hosted. if hosted on IISExp then its visbible in if IISExp is launched from console according to stackoverflow.
            System.Diagnostics.Debug.WriteLine("#> DIAG: Ns"); //writes to the VS output console
        }
    }
}

namespace TestExtNs
{
    public static class TesterExt
    {
        public static void TestExt(this YourBlazorProject.Server.TestMe y)
        {
            Console.WriteLine("ExtNs");
            System.Diagnostics.Debug.WriteLine("#> DIAG: ExtNs");
        }
    }
}

Output:
Ns
Ns
ExtNs

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