Атрибут с конструктором params object[] дает несогласованные ошибки компилятора

Я получаю ошибку

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

Обратите внимание на скриншот ниже:

Обратите внимание, что если я использую атрибут DataRow с одним или тремя параметрами, я не получаю ошибку компиляции. Но если я использую два параметра, а второй параметр представляет собой массив строк, я получаю ошибку компиляции.

Подписи для DataRowAttribute являются public DataRowAttribute (object data1); а также public DataRowAttribute (object data1, params object[] moreData);

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

Я считал, что, возможно, params object[] может быть причиной некоторого замешательства. Возможно это не могло определить, имел ли я в виду [DataRow(new[] { 1 }, new[] { "1" })] или же [DataRow(new[] { 1 }, "1")]

Чтобы решить эту проблему, я попытался привести второй атрибут к object ([DataRow(new[] { 1 }, (object)new[] { "1" })]), но ошибка не исчезла, и она предупредила меня, что приведение было избыточным. Я также попытался указать типы массива явно, но это также не помогло.

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

2 ответа

Решение

tldr:

Правильный обходной путь - сказать компилятору не использовать расширенную форму:

[DataRow(new[] { 1 }, new object[] { new[] { "1" } })]

Чрезмерный анализ:

Ответ Майкла Рэндалла в основном правильный. Давайте углубимся в упрощение вашего примера:

using System;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MyAttribute : Attribute {
    public MyAttribute(params object[] x){}
}
public class Program
{
    [MyAttribute()]
    [MyAttribute(new int[0])]
    [MyAttribute(new string[0])] // ERROR
    [MyAttribute(new object[0])]
    [MyAttribute(new string[0], new string[0])]  
    public static void Main() { }
}

Давайте сначала рассмотрим случаи без ошибок.

    [MyAttribute()]

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

    [MyAttribute(new object[0])]

Далее, как насчет

    [MyAttribute(new int[0])]

? Теперь мы должны решить, применим ли конструктор в его обычной или расширенной форме. Это не применимо в нормальной форме, потому что int[] не конвертируется в object[], Это применимо в расширенной форме, так что это скомпилировано, как если бы вы написали

    [MyAttribute(new object[1] { new int[0] } )]

Теперь насчет

    [MyAttribute(new object[0])]

Конструктор применим как в обычном, так и в расширенном виде. При таких обстоятельствах побеждает нормальная форма. Компилятор генерирует вызов как написано. Он НЕ оборачивает массив объектов во второй массив объектов.

Как насчет

    [MyAttribute(new string[0], new string[0])]  

? Слишком много аргументов для нормальной формы. Расширенная форма используется:

    [MyAttribute(new object[2] { new string[0], new string[0] })] 

Это все должно быть просто. Что тогда не так с:

    [MyAttribute(new string[0])] // ERROR

? Ну, во-первых, это применимо в нормальной или расширенной форме? Очевидно, это применимо в развернутом виде. Что не так очевидно, так это то, что оно применимо и в нормальной форме. int[] неявно преобразуется в object[] но string[] делает! Это небезопасное ковариантное преобразование ссылок на массивы, и оно занимает первое место в списке "худших возможностей C#".

Поскольку разрешение перегрузки говорит о том, что это применимо как в нормальной, так и в расширенной форме, нормальная форма выигрывает, и это скомпилировано, как если бы вы написали

[MyAttribute((object[]) new string[0] )] // ERROR

Давайте рассмотрим это. Если мы изменим некоторые из наших рабочих случаев выше:

    [MyAttribute((object[])new object[0])] // SOMETIMES ERROR!
    [MyAttribute((object[])new object[1] { new int[0] } )]
    [MyAttribute((object[])new object[2] { new string[0], new string[0] })]

Все они теперь терпят неудачу в более ранних версиях C# и преуспевают в текущей версии.

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

Разрешены приведения, которые могут быть обработаны анализом значения постоянной времени компиляции; ты можешь сделать

[MyAttribute(new int[1] { (int) 100} )]

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

Как насчет другого случая, о котором вы упомянули? Это интересный!

[MyAttribute((object)new string[0])]

Опять же, давайте рассмотрим это до конца. Это применимо только в расширенной форме, поэтому это должно быть скомпилировано так, как если бы вы написали

[MyAttribute(new object[1] { (object)new string[0] } )]

Но это законно. Чтобы быть последовательными, либо обе эти формы должны быть законными, либо обе должны быть незаконными - честно говоря, мне на самом деле все равно - но странно, что одна законна, а другая нет. Рассмотрите возможность сообщения об ошибке. (Если это на самом деле ошибка, возможно, это моя вина. Извините.)

Короче говоря, смешивание объекта params [] с аргументами массива - это путаница. Постарайся избежать этого. Если вы находитесь в ситуации, когда вы передаете массивы в метод params object[], вызывайте его в его обычной форме. Делать new object[] { ... } и поместите аргументы в массив самостоятельно.

Предполагая, что ваш конструктор

public Foo(params object[] vals) { }

Тогда я думаю, что вы столкнулись с каким-то упущенным и неочевидным компилятором Dark Magic.

Например, очевидно, что ниже будет работать

[Foo(new object[] { "abc", "def" },new object[] { "abc", "def" })]
[Foo(new string[] { "abc", "def" },new string[] { "abc", "def" })]

Это также работает для меня

[Foo(new [] { 2 }, new [] { "abc"})]
[Foo(new [] { 1 }, new [] { "a"})]

Однако это не

[Foo(new [] { "a" })]
[Foo(new [] { "aaa"})]
[Foo(new string[] { "aaa" })]

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

Я думаю, что главная часть информации здесь -

Метод с массивом параметров может быть вызван в "нормальной" или "расширенной" форме. Нормальная форма такая, как будто не было "params". Расширенная форма принимает параметры и объединяет их в массив, который генерируется автоматически. Если обе формы применимы, то нормальная форма побеждает расширенную форму.

В качестве примера

PrintLength(new string[] {"hello"}); // normal form
PrintLength("hello"); // expanded form, translated into normal form by compiler.

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

Однако я думаю, что это становится еще более грязным с object[] и даже атрибуты.

Я не собираюсь делать вид, что знаю точно, что делает CLR (и есть много более квалифицированных людей, которые могут ответить). Однако для справки, взгляните на похожие ответы мастера CLR SO Эрика Липперта для более детального освещения того, что может происходить

C# params object[] странное поведение

Почему params ведет себя так?

Есть ли способ отвлечь myFunc(1, 2, 3) от myFunc(new int[] { 1, 2, 3 })?

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