Как принудительно использовать метод расширения вместо метода экземпляра с параметрами?

Мне сложно заставить компилятор С# вызвать метод расширения, который я создал, поскольку вместо этого он предпочитает метод экземпляра с аргументом 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.");
    }
}

В моей примерной программе:

pubic 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", ... ") будет с благодарностью.

Ответ 1

Вы не можете использовать расширения для "захвата" существующих методов класса.

Если вызов работает без расширения, поведение не должно меняться при добавлении расширения. Причина этого заключается в том, что существующий код не должен прерываться, введя расширение позже.

Для этого вам нужно использовать другое имя или использовать типы параметров, отличные от метода класса.

Ответ 2

Вы не можете напрямую. Метод в целевом экземпляре всегда предпочтительнее метода расширения. только способ сделать это (сохраняя имена одинаковыми и т.д.), Использовать подход CExtensions.Trace (в вопросе).

В некоторых случаях здесь может быть использован некоторый базовый класс C или интерфейса, который реализуется C, но , у которого нет a Trace, и re введите переменную и добавьте перегрузку на CExtensions, т.е.

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

Ответ 3

Tuple<string, decimal> не совпадает с KeyValuePair<string, decimal>. Следовательно, KeyValuePair<string, decimal> передается в качестве объекта, поэтому используется метод member с params object[] args.

Фактически KeyValuePair является структурой.

Ответ 4

Вы можете сделать это с помощью именованных аргументов:

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)
    });

Ответ 5

Я думаю, вы могли бы изменить первый тип параметра или имя функции (TraceFormat?)

Эта версия params выглядит очень жадной, поэтому, если вы сохраните первый аргумент как строку, он всегда будет улавливать все вызовы и игнорировать метод расширения, который я считаю.