2013-04-02 3 views
16

Я знаю, что модификатор params (который превращается в один параметр типа массива в так называемый «массив параметров»), в частности, не является частью сигнатуры метода. Теперь рассмотрим этот пример:Изменение модификатора params в переопределении метода

class Giraffid 
{ 
    public virtual void Eat(int[] leaves) 
    { 
     Console.WriteLine("G"); 
    } 
} 
class Okapi : Giraffid 
{ 
    public override void Eat(params int[] leaves) 
    { 
     Console.WriteLine("O"); 
    } 
} 

Это компилируется без предупреждений. Потом сказал:

var okapi = new Okapi(); 
okapi.Eat(2, 4, 6); // will not compile! 

выдает ошибку (No overload for method 'Eat' takes 3 arguments).

Теперь я знаю, что компилятор переводит модификатор params в приложение System.ParamArrayAttribute на соответствующий параметр. В общем случае нет никакой проблемы при применении одной коллекции атрибутов к параметру виртуального метода, а затем декорирования «соответствующего» параметра в переопределяющем методе в производном классе с другим набором атрибутов.

Однако компилятор решительно игнорирует ключевое слово params. И наоборот, если сделать это наоборот, и применяет params к параметру в базовом классе Giraffid, а затем опускает ключевое слово в переопределении в Okapi, компилятор решает украсить как методами с System.ParamArrayAttribute. Конечно, я проверял эти вещи с IL DASM.

Мой вопрос:

Является ли это документально поведение? Я тщательно изучил спецификацию языка C#, не найдя упоминания об этом.

Я могу сказать, что по крайней мере среда разработки Visual Studio смущена этим. При вводе 2, 4, 6 в вышеупомянутом методе вызов intellisense показывает мне void Okapi.Eat(params int[] leaves) в конце.


Для Comparision, я также попытался реализация метод интерфейса и изменяя наличие/отсутствие params в интерфейсе и реализация класса, и я попытался определение типа делегата и изменение params или нет ни в определении типа делегата или метод, группа методов которого я назначен переменной моего типа делегата. В этих случаях было вполне возможно изменить params -ness.

ответ

16

Поведение компилятора верное, но это немного беспорядок. Я бы предпочел, чтобы это было как минимум предупреждением.

Неудивительно, что вы не можете найти, где в спецификации указано, что это правильно. Соответствующими битами являются:

Обработка времени обработки вызова метода формы M (A), где M - группа методов, а A - необязательный список аргументов, состоит из следующих шагов: Построен набор методов-кандидатов для вызова метода. Для каждого метода F, связанного с группой методов M, если F не является общим, F является кандидатом, когда M не имеет списка аргументов типа, а F применим по отношению к A.

Что такое «методы» связанных с группой методов M "? Ну, во-первых, что такое группа методов?

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

Итак, каковы правила поиска членов?

В противном случае набор состоит из всех доступных элементов с именем N в T, включая унаследованные элементы и доступные члены с именем N в объекте. Члены, которые включают модификатор переопределения, исключаются из набора.

Акцент добавлен.

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

virtual void M(int x, int y) { } 
... 
override void M(int y, int x) { } 
... 
M(x = 1, y = 2); 

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

Короче говоря, для целей определения того, является ли метод «PARAMS» или нет, анализ делается на оригинального метода, а не на методе наиважнейшей.

Было бы хорошо, если бы компилятор дал вам предупреждение здесь.

можно сказать, что по крайней мере, среда визуальной разработки студии запутался об этом

Correct. Уровень IntelliSense всегда отображает информацию метода для метода переопределения, а не переопределенный метод. Исследования показали, что пользователи сбивают с толку, когда методы были сделаны так, как будто они были первоначально объявляющим методом, а не основным методом. И, конечно, как я упоминал ранее, это имена параметров, которые вы собираетесь использовать для именованных аргументов.

+2

Обратите внимание, что это также отвечает за то, почему он работает только с интерфейсами (пример кода в двух экземплярах в http://stackoverflow.com/questions/27843804/compiling-generic-interface-vs-generic-abstract-class-params-keyword) - нет «переопределить», поэтому метод с параметрами '* не исключается * из списка возможных совпадений. –

5

Я думаю, что это описано в 1.6.6.4 пункте C# спецификация:

виртуальный метод может быть переопределен в производном классе. Когда объявление метода экземпляра включает модификатор переопределения, метод переопределяет унаследованный виртуальный метод с той же сигнатурой. В то время как объявление виртуального метода вводит новый метод, переопределение Объявление метода специализирует существующий унаследованный виртуальный метод на , обеспечивающий новую реализацию этого метода.

В соответствии с этим, объявление метода virtual действительно важно здесь. И объявление метода virtual используется для каждого вызова этого метода.Правильно override n реализаций (если задано) взяты во время выполнения, где params ничего общего не имеет.

Это может быть подтверждено путем простого теста:

class Giraffid 
{ 
    public virtual void Eat(params int[] leaves) 
    { 
     Console.WriteLine("G"); 
    } 
} 
class Okapi : Giraffid 
{ 
    public override void Eat(int[] leaves) 
    { 
     Console.WriteLine("O"); 
    } 
} 

С этой декларации

var o = new Okapi(); 
o.Eat(1, 2, 3); 

работает 100% штрафа.

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

  • Нет связанных вопросов^_^