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. Спецификация языка вообще не нуждается в каких-либо изменениях. Я должен получить какой-то странный значок, имхо!