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




.net-attributes (6)

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 soporta atributos genéricos. Sin embargo, después de muchas búsquedas en Google, parece que no puedo encontrar la razón.

¿Alguien sabe por qué los tipos genéricos no pueden derivar de un Attribute ? ¿Alguna teoría?


Answers

Bueno, no puedo responder por qué no está disponible, pero puedo confirmar que no es un problema de CLI. La especificación de 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 la prohíbe - sección 10.1.4 "Especificación de base de clase" no da ninguna justificación.

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

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

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


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. Dado que el atributo puede afectar a 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.


Esta es una muy buena pregunta. En mi experiencia con los atributos, creo que la restricción está en su lugar porque al reflexionar sobre un atributo crearía una condición en la que tendría que verificar todas las permutaciones de tipo posibles: 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.

Tal vez una mejor clase de validación que tome un SomeCustomValidationDelegate o un ISomeCustomValidator como un parámetro sería un mejor enfoque.


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

Esto no es realmente genérico y aún debe escribir una clase de atributo específica por tipo, pero puede utilizar una interfaz base genérica para codificar un poco a la defensiva, escribir un código menor que el requerido, obtener los 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
{

}

C # (antes de C # 6) no admite las "excepciones filtradas" de CIL, lo que hace VB, por lo que en C # 1-5 una razón para volver a lanzar una excepción es que no tiene suficiente información en el momento de la captura () para determinar si realmente quería atrapar la excepción.

Por ejemplo, en VB puedes hacer

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

... que no manejaría MyExceptions con diferentes valores de ErrorCode. En C # antes de v6, tendría que atrapar y volver a lanzar MyException si el ErrorCode no era 123:

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

Desde C # 6.0 puedes filtrar igual que con VB:

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}




c# generics .net-attributes