¿Por qué C # prohíbe los tipos de atributos genéricos?

510

Esto provoca una excepción en tiempo de compilación:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Me doy cuenta de que C # no admite atributos genéricos. Sin embargo, después de mucho buscar en Google, parece que no puedo encontrar la razón.

¿Alguien sabe por qué los tipos genéricos no pueden derivar Attribute? Alguna teoria?

Bryan Watts
fuente
17
Podrías hacer [Valida (typeof (string)] - Estoy de acuerdo en que los genéricos serían más agradables ...
Consulte a Utah el
20
A pesar de que esta es una adición muy tardía a esta pregunta, es triste que no solo los atributos en sí mismos sino también las clases de atributos abstractos (que obviamente no se puedan instanciar como atributos) no estén permitidos, de esta manera: lo abstract class Base<T>: Attribute {}que podría usarse para crear clases derivadas genéricas como esta:class Concrete: Base<MyType> {}
Lucero
88
Ansío los atributos genéricos y los atributos que aceptan lambdas. Imagina cosas como [DependsOnProperty<Foo>(f => f.Bar)]o [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń
3
Esto sería extremadamente útil en una situación que acabo de encontrar; Sería bueno crear un LinkedValueAttribute que aceptara un tipo genérico y aplicara ese tipo en el valor real especificado. Podría usarlo para enumeraciones para especificar el valor "predeterminado" de otra enumeración que debería usarse si se elige este valor de enumeración. Se pueden especificar varios de estos atributos para diferentes tipos, y podría obtener el valor que necesito en función del tipo que necesito. Puedo configurarlo para usar Tipo y Objeto, pero estar fuertemente tipado sería una gran ventaja.
KeithS
10
Si no te importa un poco de IL, esto parece prometedor .
Jarrod Dixon

Respuestas:

358

Bueno, no puedo responder por qué no está disponible, pero puedo confirmar que no es un problema de CLI. La especificación CLI no lo menciona (por lo que puedo ver) y si usa IL directamente, puede crear un atributo genérico. La parte de la especificación C # 3 que lo prohíbe: la sección 10.1.4 "Especificación base de clase" no da ninguna justificación.

La especificación de ECMA C # 2 anotada tampoco proporciona información útil, aunque proporciona un ejemplo de lo que no está permitido.

Mi copia de la especificación C # 3 anotada debería llegar mañana ... Veré si eso da más información. De todos modos, definitivamente es una decisión de idioma en lugar de una decisión de tiempo de ejecución.

EDITAR: Respuesta de Eric Lippert (parafraseado): ninguna razón en particular, excepto para evitar la complejidad tanto en el lenguaje como en el compilador para un caso de uso que no agrega mucho valor.

Jon Skeet
fuente
139
"excepto para evitar la complejidad, tanto en el lenguaje y el compilador" ... y que a partir de las personas que nos da Co y contravarianza ...
FLQ
254
¿"caso de uso que no agrega mucho valor"? Esa es una opinión subjetiva, ¡podría proporcionarme mucho valor!
Jon Kruger
34
Lo que más me molesta de no tener esta función es no poder hacer cosas como [PropertyReference (x => x.SomeProperty)]. En cambio, necesitas cadenas mágicas y typeof (), lo que creo que apesta.
Asbjørn Ulsberg
13
@John: Creo que subestimas enormemente el costo de diseñar, especificar, implementar y probar una nueva función de lenguaje.
Jon Skeet
14
Solo quiero agregar en la defensa de @ Timwi que este no es el único lugar en el que se está discutiendo , y las 13K opiniones sobre esta pregunta implican un nivel saludable de interés. Además: gracias por obtener una respuesta autorizada, Jon.
Jordan Gray
84

Un atributo decora una clase en tiempo de compilación, pero una clase genérica no recibe su información de tipo final hasta el tiempo de ejecución. Como el atributo puede afectar la compilación, debe estar "completo" en el momento de la compilación.

Consulte este artículo de MSDN para obtener más información.

Galáctico vaquero
fuente
3
El artículo reitera que no son posibles, pero sin razón. Conceptualmente entiendo tu respuesta. ¿Conoces más documentación oficial sobre el tema?
Bryan Watts el
2
El artículo cubre el hecho de que el IL todavía contiene marcadores de posición genéricos que se sustituyen con el tipo real en tiempo de ejecución. El resto fue inferido por mí ... :)
GalacticCowboy
1
Para lo que vale, VB aplica la misma restricción: "Las clases que son genéricas o contenidas en un tipo genérico no pueden heredar de una clase de atributo".
GalacticCowboy
1
ECMA-334, sección 14.16 dice "Se requieren expresiones constantes en los contextos enumerados a continuación y esto se indica en la gramática mediante el uso de expresiones constantes. En estos contextos, se produce un error en tiempo de compilación si una expresión no se puede evaluar completamente en la compilación- hora." Los atributos están en la lista.
GalacticCowboy
44
Esto parece ser contradicho por otra respuesta que establece que IL lo permitirá. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
22

No sé por qué no está permitido, pero esta es una posible solución

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}
GeekyMonkey
fuente
3
Desafortunadamente, pierde la escritura en tiempo de compilación cuando consume el atributo. Imagine que el atributo crea algo del tipo genérico. Puedes solucionarlo, pero sería bueno; Es una de esas cosas intuitivas que te sorprenden que no puedes hacer, como la varianza (actualmente).
Bryan Watts
14
Lamentablemente, tratar de no hacer esto es por eso que encontré esta pregunta SO. Supongo que tendré que seguir lidiando con typeof. Realmente parece una palabra clave sucia ahora después de que los genéricos han existido durante tanto tiempo.
Chris Marisic
13

Esto no es realmente genérico y todavía tiene que escribir una clase de atributo específica por tipo, pero puede usar una interfaz base genérica para codificar un poco a la defensiva, escribir un código menor del requerido, obtener beneficios del polimorfismo, etc.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}
nawfal
fuente
8

Esta es una muy buena pregunta. En mi experiencia con atributos, creo que la restricción está en su lugar, porque al reflexionar sobre un atributo que crearía una condición en la que tendría que comprobar si todas las permutaciones posibles de tipo: typeof(Validates<string>), typeof(Validates<SomeCustomType>), etc ...

En mi opinión, si se requiere una validación personalizada según el tipo, un atributo puede no ser el mejor enfoque.

Quizás una clase de validación que tome a SomeCustomValidationDelegateo a ISomeCustomValidatorcomo parámetro sería un mejor enfoque.

Ichiban
fuente
Estoy de acuerdo contigo. He tenido esta pregunta durante mucho tiempo y actualmente estoy construyendo un sistema de validación. Utilicé mi terminología actual para hacer la pregunta, pero no tengo intención de implementar un enfoque basado en este mecanismo.
Bryan Watts
Me topé con esto mientras trabajaba en un diseño para el mismo objetivo: la validación. Estoy tratando de hacerlo de una manera que sea fácil de analizar tanto de forma automática (es decir, podría generar un informe que describa la validación en la aplicación para su confirmación) como por un humano que visualice el código. Si no son atributos, no estoy seguro de cuál sería la mejor solución ... Todavía podría intentar el diseño de atributos, pero declarar manualmente los atributos específicos del tipo. Es un poco más trabajo, pero el objetivo es la fiabilidad de conocer las reglas de validación (y poder informar sobre ellas para su confirmación).
bambams
44
podría verificar las definiciones de tipo genérico (es decir, typeof (Validates <>)) ...
Melvyn
5

Actualmente, esta no es una característica del lenguaje C #, sin embargo, hay mucha discusión sobre el repositorio oficial del lenguaje C # .

De algunas notas de la reunión :

Aunque esto funcionaría en principio, hay errores en la mayoría de las versiones del tiempo de ejecución para que no funcione correctamente (nunca se ejerció).

Necesitamos un mecanismo para comprender en qué tiempo de ejecución de destino funciona. Necesitamos eso para muchas cosas, y actualmente lo estamos viendo. Hasta entonces, no podemos soportarlo.

Candidato para una versión principal de C #, si podemos hacer que un número suficiente de versiones de tiempo de ejecución se encargue de ello.

Owen Pauling
fuente
1

Mi solución es algo como esto:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }
razon
fuente