Подтвердить что ты не робот

Являются ли методы расширения для интерфейсов более низкими приоритетами, чем менее конкретные?

У меня есть следующий класс расширений:

public static class MatcherExtensions
{
    public static ExecMatcher<T1, T2> Match<T1, T2>(this Tuple<T1, T2> item)
    {
        return new ExecMatcher<T1, T2>(item.Item1, item.Item2);
    }

    public static ExecMatcher<T1, T2> Match<T1, T2>(this ITupleMatchable<T1, T2> item)
    {
        var tuple = item.PropertiesToMatch;
        return new ExecMatcher<T1, T2>(tuple.Item1, tuple.Item2);
    }

    public static ExecMatcher<T> Match<T>(this T item) { return new ExecMatcher<T>(item); }
}

Если я создаю кортеж и вызываю Match(), он правильно использует первый метод расширения:

var tuple = Tuple.Create(1, "a");
tuple.Match().With(1, "a")...   // compiles just fine.

Если я создаю int и вызываю Match(), он правильно использует последний метод расширения:

var one = 1;
one.Match().With(1)... // compiles just fine.

Однако, если я создаю SomeClass, который реализует ITupleMatchable<int, string> и пытается и сопоставляется с ним, компилятор по-прежнему выбирает третий метод расширения, а не второй, несмотря на то, что последнее является более конкретным:

var obj = new SomeClass(1, "a");
obj.Match().With(1, "a")... // won't compile.

На основе Эрик Липперт отвечает на аналогичный вопрос, я работал над этим, добавив третий метод расширения в свой собственный класс в подкаталог, тем самым создав более длинный пространство имен и так далее "расстояние" между ним и вызывающим кодом, чем для версии, характерной для ITupleMatchable<T1, T2>. Это похоже на взломать меня. Есть ли более простой способ разрешить это?

4b9b3361

Ответ 1

Если вы просто нажмете new SomeClass(1, "a") на ITupleMatchable<int, string>, он будет работать нормально:

var obj = (ITupleMatchable<int, string>)new SomeClass(1, "a");
obj.Match().With(1, "a");

Помните, что ваша переменная obj имеет тип времени SomeClass. Компилятор может "легче" сопоставить фактический класс с третьим методом расширения (который совместим с любым типом), чем он может, если посмотреть на реализацию интерфейса SomeClass, а затем сопоставить его, например, второй метод расширения.

Но если вы предоставите параметр this в качестве фактического типа интерфейса, то второй метод расширения лучше подходит, потому что он именно тот тип, который ищет метод, а не более широкий "любой тип". То есть это более конкретная декларация, и поэтому она "лучше".

Обратите внимание, что после обнаружения набора кандидатов методов расширения (с помощью правил, относящихся к пространству имен и т.д.), фактический метод определяется с использованием нормального разрешения перегрузки. То есть что хотя бы один метод в вашем классе MatcherExtensions является подходящим методом расширения, тогда компилятор переходит с нормальными правилами разрешения перегрузки, чтобы выбрать среди них. Эти правила можно найти в спецификации С# 5.0, раздел 7.5.3.

Вкратце: прежде чем применять правила разрешения перегрузки (действительно, чтобы определить, какие методы даже подходят), обратите внимание, что компилятор уже определил параметры типового типа. Таким образом, поскольку он оценивает разрешение перегрузки, он смотрит на Match(SomeClass item) и Match(ITupleMatchable<int, string> item). Надеюсь, когда вы это уделите, вы поймете, почему, если переменная имеет тип SomeClass, компилятор выбирает ваше третье расширение предпочтительно над вторым, а наоборот, если тип ITupleMatchable<int, string>.