6

¿Se puede usar un atributo en C# con un inicializador de recopilación?¿Puedo usar un inicializador de colección para un atributo?

Por ejemplo, me gustaría hacer algo como lo siguiente:

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

Sé atributos se han nombrado los parámetros, y ya que parece bastante similar a los inicializadores de objeto, me preguntaba si eran inicializadores de colección disponible también

+0

Scott, lo siento - Mi respuesta es no. No me gano el voto. Por favor vote abajo porque es un juego limpio. – vladimir77

+0

@ vladimir77 - ¡No debería haber borrado su respuesta! A veces la respuesta incorrecta sigue siendo útil ... simplemente actualice su respuesta la próxima vez, para que la gente pueda entender por qué está mal. –

+0

Ok. :) Lo he restaurado. – vladimir77

Respuesta

6

Actualizar : Siento que me equivoque - pass variedad de tipo personalizado es imposible :(

Los tipos de parámetros de posición y el nombre de una clase de atributo se limitan a los tipos de parámetros de atributos, que son:

  1. Uno de los siguientes tipos: bool, byte, char, doble, flotador, int, largo, corto, cadena.
  2. El tipo de objeto.
  3. El tipo System.Type.
  4. Un tipo Enum, siempre que tenga acceso público y los tipos en los que está anidado (si existe) también tienen acceso público (Sección 17.2).
  5. Arrays unidimensionales de tipos anteriores. source

Fuente: stackoverflow.

Se puede declarar que pasa una matriz de un tipo personalizado:

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) 
    { 
    // 
    } 
} 

pero compilación se producen errores en la declaración de atributo:

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

} 
+0

Como tiene los 'params ', puede omitir' new [] {...} 'para obtener una sintaxis mucho más agradable. Sin embargo, ¿estás seguro de que compila? Creo que los argumentos de los atributos tienen límites: expresión constante, tipo de expresión o expresión de creación de matriz, como mencionó @Jason Down. –

+1

@Scott: esa _es_ una expresión de creación de matriz. :) (Por cierto, voy a eliminar mi respuesta, como Jason señaló que no es bueno.) –

+0

Lo siento Im error :( – vladimir77

3

La respuesta corta es no.

Respuesta más larga: Para que una clase sea compatible con los inicializadores de recopilación, debe implementar IEnumerable y debe tener un método de agregar. Así, por ejemplo:

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(); 
    } 
} 

puedo entonces hacer esto:

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

así que el uso de esta, vamos a tratar de hacer que funcione para un atributo. (Nota al margen, ya que los atributos no son compatibles con los genéricos, sólo estoy hardcoding que el uso de cadenas para la prueba):

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(); 
    } 
} 

Y ahora para probarlo:

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

} 

y que no se compila :(No estoy seguro de dónde está exactamente la limitación, supongo que los atributos no se renuevan del mismo modo que un objeto común y, por lo tanto, no es compatible. Me gustaría saber si esto puede ser apoyado teóricamente por una versión futura del lenguaje ...

+0

¡Gracias por su respuesta corta y larga! Eso es exactamente lo que intenté también, pero cuando no compilaba, no estaba seguro de si era una sintaxis defectuosa o una característica no compatible. Tampoco sabía que los genéricos no estaban permitidos. –

+0

FYI me gusta su respuesta mucho, pero decidí aceptar la respuesta de @ vladimir77 sólo porque tiene una respuesta definitiva citado de MSDN, y parece ser la mejor respuesta para la pregunta. –

+0

@scott: no es gran cosa. Solo quiero señalar que esto no es un problema de parámetros con nombre, ya que eso NO es lo que estabas tratando de hacer. Que estaba tratando de utilizar la sintaxis colección intializer, que es una cosa separada por completo ... – BFree

5

Sección 17.1.3 de la especificación de C# 4.0 no lo hace específicamente permitir matrices multidimensionales dentro de los parámetros de atributos, así que mientras Foo (cadena [,] bar) podría permitir que llame Foo (nueva [,] {{ "a", "b"}, { "clave2", "val2"} }), desafortunadamente, no está disponible para los atributos.

Así que con esto en mente, algunas posibilidades para aproximar lo que quiere son:

  1. Utilice un unidimensional matriz, con alternancia de pares de clave y valor. La desventaja obvia de este enfoque es que no está aplicando exactamente nombres y valores.

  2. Permita que su parámetro que aparezca varias veces mediante el etiquetado de su definición de atributo con el atributo siguiente:

    [AttributeUsage(AllowMultiple=true)] 
    

    De esta manera, ahora se puede definir:

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

    Esto es un poco más prolijo de lo que estoy seguro que esperabas, pero hace una delineación clara entre nombres y valores.

  3. encontrar un paquete JSON y proporcionan un inicializador para su atributo. El golpe de rendimiento es irrelevante ya que esto se hace durante la inicialización del código. Usando Newtonsoft.Json, por ejemplo, usted podría hacer un atributo de este modo:

    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; 
         } 
        } 
    

    que luego permitirá una instancia de un atributo de este modo:

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

    Sé que es un poco de Cotización en feliz , mucho más involucrado, pero ahí estás. A pesar de todo, en este mundo loco y dinámico, saber cómo inicializar objetos con JSON no es una mala habilidad para tener en el bolsillo trasero.

+0

Gracias por sus sugerencias ! # 1 parece una solución decente. # 2 es una gran solución para algunos escenarios, pero no funciona para todos los escenarios. Y # 3 ... habla de exageración, pero gracias por pensar fuera de la caja! –

+0

Curiosamente, es más probable que la solución n. ° 3 supere la prueba del tiempo, ya que cualquier atributo adicional para el que quiera utilizar esta técnica se puede derivar directamente de JsonAttribute. Pero si lo que busca es una solución que no compila, algunas de las otras respuestas serán más de tu agrado. ;-) –

+0

Eso es cierto, tus soluciones son las únicas que compilan :) –

Cuestiones relacionadas