C# params очевидная ошибка компилятора (C# 5.0)

Это продолжение темы, которая, как я думал, была решена вчера. Вчера у меня были проблемы с моим кодом в следующем случае:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    class Program
    {
        class Bar
        {
            int v;

            public Bar(int v) { this.v = v; }
            public override string ToString() { return v.ToString(); }
        }

        static void Main(string[] args)
        {
            Foo(1, 2, 3);
            Foo(new int[] { 1, 2, 3 });
            Foo(new Bar(1), new Bar(2), new Bar(3));
            Foo(new Bar[] { new Bar(1), new Bar(2), new Bar(3) });
            System.Threading.Thread.Sleep(20000);
        }

        static void Foo(params object[] objs)
        {
            Console.WriteLine("New call to Foo: ");
            foreach(object o in objs)
                Console.WriteLine("Type = " + o.GetType() + ", value = "+o.ToString());
        }
    }
}

Если вы запустите это, вы увидите проблему с последним вызовом Foo. Тот факт, что аргумент является вектором, "потерян".

Итак.... кто-нибудь знает, как сообщить об ошибке компилятора C#? Или это будет считаться ошибкой отражения?

(Какое облегчение: я был огорчен, когда подумал, что потратил здесь время на собственную ошибку. На самом деле это ошибка C#, и я оправдан! И как часто мы видим реальные ошибки компилятора C# в эти дни? Не часто...)

5 ответов

Решение

Я ожидаю, что эти два вызова будут функционировать одинаково - аргумент params - это массив в вызываемом методе. Пример Джона Скита в предыдущем вопросе работает, потому что массив int не ковариантен массиву объектов (и поэтому рассматривается как new Object[] { new Int[] {1,2,3} }), но в этом примере массив FooBars ковариантен массиву объектов, поэтому ваш параметр раскрывается в objs аргумент.

Википедия всех вещей охватывает этот конкретный случай: ковариантность и контравариантность (информатика)

Извините, но я уверен, что это не ошибка компилятора.

РЕДАКТИРОВАТЬ:

Вы можете достичь того, чего хотите, таким образом:

Foo(new Object[] { new Bar[] { new Bar(1), new Bar(2), new Bar(3) } });

NEW EDIT (другой автор):

Или просто используйте:

Foo((Object)new Bar[] { new Bar(1), new Bar(2), new Bar(3) });

Спецификация C# 4.0 довольно явно показывает, как все это работает. 7.5.3.1 говорит, что если функция с params может быть применен либо в нормальной форме (игнорируя params ключевое слово) или расширенную форму (используя params ключевое слово), то побеждает нормальная форма.

Если предположить, Foo был объявлен как Foo(params object[] args)тогда звонок Foo(new Foobar[] {new Foobar(1), new Foobar(2), new Foobar(3)) }); применимо в нормальной форме, так как Foobar[] неявно конвертируется в object[] (6.1.6 пункт 5). Поэтому используется нормальная форма, а расширенная форма игнорируется.

(Я предполагаю, что C# 5.0 не изменил эту часть языка.)

Смотрите раздел 7.5.3.1 спецификации C#:

7.5.3.1 Применимый член функции

Считается, что член функции является применимым членом функции по отношению к списку аргументов A, если все из следующего является истинным:

  • Каждый аргумент в A соответствует параметру в объявлении члена функции, как описано в §7.5.1.1, и любой параметр, которому не соответствует ни один аргумент, является необязательным параметром.
  • Для каждого аргумента в A режим передачи параметров этого аргумента (т. Е. Value, ref или out) идентичен режиму передачи параметров соответствующего параметра, и
    • для параметра значения или массива параметров существует неявное преобразование (§6.1) из аргумента в тип соответствующего параметра, или
    • [... некоторые не относящиеся к делу материалы, касающиеся ref а также out параметры...]

Для члена функции, который включает в себя массив параметров, если член функции применим по вышеуказанным правилам, он считается применимым в его обычной форме. Если член функции, который включает в себя массив параметров, не применим в его обычной форме, вместо этого член функции может быть применим в его расширенной форме [.]

Потому что массив, который вы передали, может быть неявно преобразован в object[]и поскольку разрешение перегрузки предпочитает "нормальную" форму, а не "расширенную", поведение, которое вы наблюдаете, соответствует спецификации, и ошибки нет.

В дополнение к обходному пути, описанному Крисом Шейном, вы также можете изменить Bar из класса в структуру; что делает массив более неявно конвертируемым в object[], так что вы получите поведение, которое вы хотите.

Я полагаю, что вы не правы. Кен. Это потому, что int[] не относится к типу object[], поэтому компилятор предполагает, что int[] является лишь одним из аргументов, передаваемых методу.

вот как:

new Foobar[] { } is object[]; // true
new int[] { } is object[]; // false

update: вы можете сделать метод универсальным, чтобы компилятор знал, что структура / тип объекта передается как params:

void Foo<T>(params T[] objs)
{
    foreach (T o in objs)
        Console.WriteLine(o.GetType());
}

Итак, я собираюсь предположить, что лучший ответ заключается в том, что я прав и что это действительно ошибка компилятора. Хотя я вижу это совершенно ясно, ваше объяснение игнорирует ключевое слово "params". Фактически, чтобы использовать ковариацию таким способом, НЕОБХОДИМО игнорировать ключевое слово params, как если бы оно было неважным. Я постулирую, что просто нет правдоподобного объяснения для компиляции кода таким образом с Params, присутствующим в качестве ключевого слова в сигнатуре типа Foo: чтобы вызвать ваше объяснение, вам нужно убедить меня, что myClass[] должен соответствовать типу object[], но мы не должны даже задавать этот вопрос, учитывая конструкцию params. На самом деле, чем больше вы об этом думаете, тем яснее, что это действительно ошибка компилятора C# 5.0: компилятор пренебрегает применением ключевого слова params. Спецификация языка вообще не нуждается в каких-либо изменениях. Я должен получить какой-то странный значок, имхо!

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