问题 如何强制使用扩展方法而不是实例方法与params?


我无法让C#编译器调用我创建的扩展方法,因为它更喜欢使用带有的实例方法 params 相反的论点。

例如,假设我有以下类及其方法:

public class C
{
    public void Trace(string format, params object[] args)
    {
         Console.WriteLine("Called instance method.");
    }
}

并且扩展:

public static class CExtensions
{
    public void Trace(this C @this, string category, string message, params Tuple<string, decimal>[] indicators)
    {
        Console.WriteLine("Called extension method.");
    }
}

在我的示例程序中:

public void Main()
{
    var c = new C();

    c.Trace("Message");
    c.Trace("Message: {0}", "foo");
    c.Trace("Category", "Message", new KeyValuePair<string, decimal>("key", 123));
}

所有电话打印 Called instance method.

我无法上课 C显然,或者我不打算创建扩展方法,我的扩展很重要,因为它允许我的用户继续使用他们已经知道的具有一些附加值的类。

根据我的理解,编译器将支持实例方法而不是扩展方法,但这是唯一的规则吗?这意味着任何具有类似方法的类 Method(string format, params object[] args) 不能有第一个参数类型的扩展方法 string

对此行为的原因或解决方法的任何解释(即  “干脆打电话 CExtensions.Trace(c, "Category", ...“)将不胜感激。


2263
2017-11-08 10:06


起源

我之前遇到过这种情况。似乎唯一的解决方案是重命名该方法。我有兴趣看看是否有修复。 - Polynomial


答案:


您不能使用扩展来“接管”现有的类方法。

如果呼叫在没有扩展名的情况下工作,则添加扩展名时不应更改行为。这样做的原因是现有代码不应该通过稍后引入扩展来破坏。

您必须为其使用不同的名称,或使用与类方法不同的参数类型。


5
2017-11-08 10:24



虽然很多人给出了很好的解决方法建议(尽管没有一个对我有用),但我认为这是最好的答案,因为它总结了我的问题所在。 - madd0


你不能 。目标实例上的方法总是优于扩展方法。该 只要 这样做的方法(保持名称相同等)是使用 CExtensions.Trace 方法(在问题中)。

在某些情况下,这里的一个技巧是使用一些基类 C 或界面 C 实施,但 没有 一个 Trace 方法,并重新键入变量,并添加重载 CExtensions,即

IFoo foo = c;
foo.Trace(...);

4
2017-11-08 10:11



我喜欢将扩展方法添加到基类型的技巧。当然,这太简单了,在我的情况下,我不得不延长 object... - madd0


Tuple<string, decimal> 是不一样的 KeyValuePair<string, decimal>。于是 KeyValuePair<string, decimal> 传入的方式就像一个对象因此成员方法 params object[] args 用来。

事实上 KeyValuePair 是一个 结构体


2
2017-11-08 10:11



Tuple 要么 KeyValuePair,它并没有真正改变太多,两种类型 - 这种类型的_any_类型 - 将被“吸收” object 阵列。 - madd0
这是真的,但如果你使用了 Tuple<string, decimal> 在调用中它仍然不会使用扩展方法。 - Guffa
是的,我刚刚测试过,它没有使用它。 - Aliostad


您可以使用命名参数执行此操作:

c.Trace(
    "Category", 
    "Message", 
    indicators: new Tuple<string, decimal>("key", 123));

但你放松了 params 功能,你需要显式传递指数参数的数组,如下所示:

c.Trace(
    "Category",
    "Message",
    indicators: new Tuple<string, decimal>[] 
    {
        new Tuple<string, decimal>("key", 123),
        new Tuple<string, decimal>("key2", 123)
    });

2
2017-11-08 10:13



它会工作,但它很容易忘记这个命名参数,我们将默默地调用默认版本,没有办法注意到这一点。 - Valentin Kuzub
@Valentin Kuzub,但其他选项也容易出错。您可能忘了需要使用不同的名称或不同的签名来调用该方法。 - João Angelo
João的论点正是为什么我不想改变方法的名称或签名,但Valentin是对的:很可能会忘记使用命名参数并调用默认版本而没有注意到(或者只是为了太晚) 。 - madd0


我猜你可以改变第一个参数类型或函数名称(TraceFormat?)

这个params版本看起来非常贪婪,所以如果你将第一个参数保存为字符串,它将始终捕获所有调用并忽略我认为的扩展方法。


0
2017-11-08 10:11



我真的不想更改方法名称,因为它是开发人员习惯的东西。更改名称意味着不会按照我们的意愿使用该方法。将第一个参数的类型更改为其他参数 string 会很难,因为我真的想要一个类别名称 string。 - madd0
实际上第一个参数作为类别听起来像是一个很好的候选人成为一个例子,它将开始工作。 - Valentin Kuzub
瓦伦丁,很好的尝试,但对于一些团队,“类别”可能是,例如,客户的名字。因为我真的希望我们的客户群不断增长,所以我宁愿避免使用 enum 这将不得不不断更新;) - madd0
我恐怕没有神奇的解决方案。选择一个不那么难看的人;) - Valentin Kuzub