Могу ли я использовать инициализатор коллекции для атрибута? - PullRequest
6 голосов
/ 04 августа 2011

Может ли атрибут в C # использоваться с инициализатором коллекции?

Например, я хотел бы сделать что-то вроде следующего:

[DictionaryAttribute(){{"Key", "Value"}, {"Key", "Value"}}]
public class Foo { ... }

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

Ответы [ 3 ]

6 голосов
/ 04 августа 2011

Обновление: Извините, я ошибаюсь - передать массив пользовательского типа невозможно: (

Типы позиционных и именованных параметров для класса атрибута:ограничено типами параметров атрибута:

  1. Один из следующих типов: bool, byte, char, double, float, int, long, short, string .
  2. Объект типа.
  3. Тип System.Type.
  4. Тип Enum, если он имеет общедоступный доступ и типы, в которые он вложен (если есть), также имеютобщедоступный доступ (раздел 17.2).
  5. одномерные массивы типов выше . источник

источник: stackoverflow .

Вы МОЖЕТЕ ОБЪЯВИТЬ, передавая массив пользовательского типа:

class TestType
{
  public int Id { get; set; }
  public string Value { get; set; }

  public TestType(int id, string value)
  {
    Id = id;
    Value = value;
  }
}

class TestAttribute : Attribute
{
  public TestAttribute(params TestType[] array)
  {
    //
  }
}

, но при объявлении атрибута возникают ошибки компиляции:

[Test(new[]{new TestType(1, "1"), new TestType(2, "2"), })]
public void Test()
{

}
5 голосов
/ 04 августа 2011

Раздел 17.1.3 спецификации C # 4.0 специально не допускает многомерных массивов внутри параметров атрибута, поэтому в то время как Foo (строка [,] bar) может позволить вам вызывать Foo (new [,] {{"a"), "b"}, {"key2", "val2"}}), к сожалению, он недоступен для атрибутов.

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

  1. Использовать одномерный массив с чередующимися парами ключей и значений.Очевидным недостатком этого подхода является то, что он не обеспечивает принудительное применение имен и значений.

  2. Позвольте вашему параметру появляться несколько раз, помечая определение атрибута следующим атрибутом:

    [AttributeUsage(AllowMultiple=true)]
    

    Таким образом, теперь вы можете определить:

    [KeyVal("key1","val1"), KeyVal("key2","val2")]
    public class Foo { ... }
    

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

  3. Найдите пакет JSON и предоставьте инициализатор для вашего атрибута.Падение производительности несущественно, так как это делается во время инициализации кода.Используя, например, Newtonsoft.Json, вы можете создать такой атрибут:

        public class JsonAttribute : Attribute
        {
          Dictionary<string, string> _nameValues = 
              new Dictionary<string, string>();
          public JsonAttribute(string jsoninit)
          {
            var dictionary = new Dictionary<string, string>();
            dynamic obj = JsonConvert.DeserializeObject(jsoninit);
            foreach(var item in obj)
              _nameValues[item.Name] = item.Value.Value;
          }
        }
    

    , который позволит вам создать экземпляр атрибута следующим образом:

    [Json(@"{""key1"":""val1"", ""key2"":""val2""}")]
    public class Foo { ... }
    

    Я знаю, что этонемного цитаты - счастливы, намного более вовлечены, но вы здесь.Несмотря на это, в этом сумасшедшем динамичном мире знание того, как инициализировать объекты с помощью JSON, не является плохим умением иметь в заднем кармане.

3 голосов
/ 04 августа 2011

Короткий ответ - нет.

Более длинный ответ: для того чтобы класс поддерживал инициализаторы коллекций, ему необходимо реализовать IEnumerable и иметь метод add. Так, например:

public class MyClass<T,U> : IEnumerable<T>
{
    public void Add(T t, U u)
    {
    }

    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Тогда я могу сделать это:

var mc = new MyClass<int, string> {{1, ""}, {2, ""}};

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

public class CollectionInitAttribute : Attribute, IEnumerable<string>
{
    public void Add(string s1, string s2)
    {

    }

    public IEnumerator<string> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

А теперь, чтобы проверить это:

[CollectionInit{{"1","1"}}]
public class MyClass
{

}

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

...