2017-01-22 3 views
9

Этот код не будет компилироваться:Неоднозначность расширения вызова метода

using System; 
using System.Runtime.CompilerServices; 

static class Extensions { 
    public static void Foo(this A a, Exception e = null, string memberName = "") { 
    } 

    public static void Foo<T>(this A a, T t, Exception e = null, string memberName = "") 
     where T : class, IB { 
    } 
} 

interface IB { } 

class A { } 

class Program { 
    public static void Main() { 
     var a = new A(); 
     var e = new Exception(); 

     a.Foo(e); //<- Compile error "ambiguous call" 
    } 
} 

Но если удалить последние string аргументов все в порядке:

public static void Foo(this A a, Exception e = null) { 
    } 

    public static void Foo<T>(this A a, T t, Exception e = null) 
     where T : class, IB { 
    } 

Вопрос - почему эти необязательные string аргументов нарушить выбор компилятора вызова метода?

Добавлено: осветленный вопрос: Я не понимаю, почему компилятор не может выбрать правильную перегрузку в первом случае, но может сделать это второй?

Отредактировано: [CallerMemberName] атрибут не является причиной проблемы здесь, так что я удалил его от вопроса.

+1

Просто для пояснения: Удаление атрибута '[CallerMemberName] только * не имеет * эффекта, компилятор выводит ошибку. Удаление всего аргумента '[CallerMemberName] string memberName =" "' * имеет * эффект, код компилируется. Это то, что я наблюдаю. Правильно? –

+4

** C# Спецификация: 7.5.3.2 Лучше член функции: ** * В противном случае, если все параметры MP имеют соответствующий аргумент, тогда как аргументы по умолчанию должны быть заменены как минимум одним необязательным параметром в MQ, тогда MP лучше, чем MQ. * Это правило применяется во втором случае, но не может быть применено в первом случае. – PetSerAl

+0

Если вы удалите атрибут '[CallerMemberName]', но сохраните параметр 'string memberName =" "' в обоих методах, то что происходит? Edit: @OndrejTucny уже сказал, что я вижу после публикации. Все еще хороший вопрос. –

ответ

6

@PetSerAl указал на спецификацию в уже комментариях, но позвольте мне перевести это на простой английском языке:

языка С # есть правило, которое говорит перегрузка без опущенных дефолтных аргументов предпочтительнее перегрузки с опущено дефолтом аргументы. Это правило делает Foo(this A a, Exception e = null) лучшим совпадением, чем Foo(this A a, T t, Exception e = null).

Язык C# не имеет правила, согласно которому перегрузка с одним пропущенным аргументом по умолчанию предпочтительнее перегрузки с двумя пропущенными аргументами по умолчанию. Поскольку у него нет такого правила, Foo(this A a, Exception e = null, string s = "") is не предпочитает более Foo<T>(this A a, T t, Exception e = null, string s = "").

Самый простой способ избежать этой проблемы, как правило, заключается в предоставлении дополнительных перегрузок вместо использования значений параметров по умолчанию. Вам нужны значения параметров по умолчанию для CallerMemberName, но вы можете предоставить дополнительные перегрузки, которые опускают Exception, и переходят к реальной реализации, передавая для этого null.

Замечание: убедитесь, что Foo<T>(this A a, T t, string s = "") не выбрано, когда доступно Foo(this A a, Exception e, string s = ""). Это будет непростая проблема. Если ваша переменная статически типизирована как Exception, то предпочтительным будет не общий метод, но если он статически введен как, например, ArgumentException, то T=ArgumentException является лучшим совпадением, чем базовый класс Exception, а ошибка в T=ArgumentException будет обнаружена слишком поздно, чтобы выбрать метод, который вы хотите вызвать. Возможно, было бы безопаснее разместить TпослеException и всегда требует исключения исключения (возможно null) при использовании общего метода.

+1

Отличный ответ. Означает ли это, что ограничения, такие как 'where T: class, IB', полностью игнорируются компилятором, когда дело доходит до разрешения перегрузки? – Szer

+2

@Szer да, [общие ограничения не являются частью сигнатуры метода.] (@ Szer yes, generic [ограничения не являются частью сигнатуры методов.] (Https://blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature /) – InBetween

+0

Вот точная цитата из спецификации языка C#: _ • \t В противном случае, если все параметры MP имеют соответствующий аргумент, тогда как аргументы по умолчанию должны замените по крайней мере один необязательный параметр в MQ, тогда MP лучше, чем MQ._ –

Смежные вопросы