Изменение модификатора params в переопределении метода

я знаю, что a 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");
    }
}

это компилируется без предупреждений. Потом сказал:"!--17-->

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

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

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

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

мой вопрос:

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

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


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

2 ответов


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

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

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

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

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

OK, так что такое поиск элемента правила?

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

Курсив.

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

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

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

короче говоря: для определения того, является ли метод "params" или нет, анализ выполняется на оригинальный способ, а не переопределение метода.

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

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

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


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

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

согласно которому, virtual объявление метода действительно важно здесь. И virtual объявление метода используется при каждом вызове этого метода. Правильно overrideN реализации (если указано) принимаются во время выполнения, где 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% нормально.