Изменение модификатора 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) в чаевые.


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

2 ответа

Решение

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

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

Обработка во время привязки вызова метода в форме 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" или нет, анализ выполняется по оригинальному методу, а не по методу переопределения.

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

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

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

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

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

В соответствии с этим, 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% нормально.

Другие вопросы по тегам