Por que o C # proíbe tipos de atributos genéricos?

510

Isso causa uma exceção em tempo de compilação:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Sei que o C # não suporta atributos genéricos. No entanto, depois de muito pesquisar no Google, parece que não consigo encontrar o motivo.

Alguém sabe por que os tipos genéricos não podem derivar Attribute? Alguma teoria?

Bryan Watts
fonte
17
Você poderia fazer [Valida (typeof (string)] - Concordo genéricos seria melhor ...
ConsultUtah
20
Embora essa seja uma adição muito tardia a essa pergunta, é triste que não apenas os atributos em si, mas também as classes de atributos abstratas (que obviamente não possam ser instanciadas como atributos de qualquer maneira) não sejam permitidas, como esta: abstract class Base<T>: Attribute {}que poderia ser usada para criar classes derivadas genéricas como esta:class Concrete: Base<MyType> {}
Lucero
88
Eu anseio por atributos genéricos e atributos que aceitem lambdas. Imagine que coisas como [DependsOnProperty<Foo>(f => f.Bar)]ou [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń
3
Isso seria extremamente útil em uma situação que acabei de encontrar; seria bom criar um LinkedValueAttribute que aceitasse um tipo genérico e aplicasse esse tipo no valor real especificado. Eu poderia usá-lo para enumerações para especificar o valor "padrão" de outra enumeração que deve ser usada se esse valor de enumeração for escolhido. Vários desses atributos podem ser especificados para tipos diferentes, e eu poderia obter o valor necessário com base no tipo necessário. Posso configurá-lo para usar Tipo e Objeto, mas ser fortemente digitado seria uma grande vantagem.
Keiths
10
Se você não se importa com um pouco de IL, isso parece promissor .
Jarrod Dixon

Respostas:

358

Bem, não sei responder por que não está disponível, mas posso confirmar que não é um problema de CLI. A especificação da CLI não a menciona (até onde posso ver) e, se você usar o IL diretamente, poderá criar um atributo genérico. A parte da especificação do C # 3 que a proíbe - seção 10.1.4 "Especificação básica da classe" não fornece nenhuma justificativa.

A especificação ECMA C # 2 anotada também não fornece informações úteis, embora forneça um exemplo do que não é permitido.

Minha cópia da especificação C # 3 anotada deve chegar amanhã ... Vou ver se isso fornece mais informações. Enfim, é definitivamente uma decisão de idioma, e não de execução.

EDIT: Resposta de Eric Lippert (parafraseado): nenhum motivo específico, exceto para evitar complexidade no idioma e no compilador para um caso de uso que não agrega muito valor.

Jon Skeet
fonte
139
"exceto para a complexidade evitar tanto na linguagem e compilador" ... e que do povo dando-nos co e contravariance ...
FLQ
254
"caso de uso que não agrega muito valor"? Essa é uma opinião subjetiva, poderia me fornecer muito valor!
Jon Kruger
34
O que mais me incomoda em não ter esse recurso é não poder fazer coisas como [PropertyReference (x => x.SomeProperty)]. Em vez disso, você precisa de strings mágicos e typeof (), o que eu acho meio que uma merda.
Asbjørn Ulsberg
13
@ John: Eu acho que você subestima amplamente o custo de projetar, especificar, implementar e testar um novo recurso de linguagem.
Jon Skeet
14
Eu só quero acrescentar na defesa de @ Timwi que esse não é o único lugar em que eles estão sendo discutidos , e as 13 mil visualizações sobre esta questão implicam um nível saudável de interesse. Também: obrigado por obter uma resposta oficial, Jon.
Jordan Gray
84

Um atributo decora uma classe no tempo de compilação, mas uma classe genérica não recebe suas informações de tipo final até o tempo de execução. Como o atributo pode afetar a compilação, ele deve estar "completo" no momento da compilação.

Consulte este artigo do MSDN para obter mais informações.

GalacticCowboy
fonte
3
O artigo reafirma que eles não são possíveis, mas sem motivo. Conceitualmente, entendo sua resposta. Você conhece mais alguma documentação oficial sobre o assunto?
21711 Bryan Watts
2
O artigo aborda o fato de a IL ainda conter espaços reservados genéricos que são substituídos pelo tipo real no tempo de execução. O resto foi inferido por mim ... :)
GalacticCowboy
1
Pelo que vale, o VB impõe a mesma restrição: "Classes genéricas ou contidas em um tipo genérico não podem herdar de uma classe de atributo".
GalacticCowboy
1
O ECMA-334, seção 14.16, diz que "Expressões constantes são necessárias nos contextos listados abaixo e isso é indicado na gramática usando expressão constante. Nesses contextos, ocorre um erro em tempo de compilação se uma expressão não puder ser totalmente avaliada na compilação - Tempo." Os atributos estão na lista.
GalacticCowboy
4
Isso parece ser contrariado por outra resposta que afirma que a IL permitirá. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
22

Não sei por que não é permitido, mas esta é uma solução possível

[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
fonte
3
Infelizmente, você perde a digitação em tempo de compilação ao consumir o atributo. Imagine que o atributo crie algo do tipo genérico. Você pode contornar isso, mas seria bom; é uma daquelas coisas intuitivas que você surpreende que não pode fazer, como variação (atualmente).
11138 Bryan Watts
14
Infelizmente, tentar não fazer isso é por que encontrei essa pergunta SO. Acho que vou ter que lidar com typeof. Parece realmente uma palavra-chave suja agora, depois que os genéricos existem há tanto tempo.
31909 Chris Marisic
13

Isso não é verdadeiramente genérico e você ainda precisa escrever uma classe de atributo específica por tipo, mas pode usar uma interface básica genérica para codificar um pouco defensivamente, escrever um código menor do que o necessário, obter benefícios do 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
fonte
8

Esta é uma pergunta muito boa. Na minha experiência com atributos, acho que a restrição está em vigor porque ao refletir sobre um atributo que iria criar uma condição em que você teria que verificar todas as permutações de tipos possíveis: typeof(Validates<string>), typeof(Validates<SomeCustomType>), etc ...

Na minha opinião, se uma validação personalizada for necessária, dependendo do tipo, um atributo pode não ser a melhor abordagem.

Talvez uma classe de validação que aceite um SomeCustomValidationDelegateou um ISomeCustomValidatorcomo parâmetro seja uma abordagem melhor.

ichiban
fonte
Eu concordo com você. Eu tenho essa pergunta há muito tempo e atualmente estou construindo um sistema de validação. Usei minha terminologia atual para fazer a pergunta, mas não tenho a intenção de implementar uma abordagem baseada nesse mecanismo.
11558 Bryan Watts #
Eu me deparei com isso enquanto trabalhava em um design para o mesmo objetivo: validação. Estou tentando fazer isso de uma maneira fácil de analisar automaticamente (ou seja, você pode gerar um relatório descrevendo a validação no aplicativo para confirmação) e por um humano visualizando o código. Se não forem atributos, não tenho certeza qual seria a melhor solução ... Ainda posso tentar o design do atributo, mas declarar manualmente atributos específicos do tipo. É um pouco mais trabalhoso, mas o objetivo é a confiabilidade de conhecer as regras de validação (e poder relatá-las para confirmação).
BamBams
4
você pode verificar contra as definições de tipo genérico (isto é, typeof (Valida <>)) ...
Melvyn
5

No momento, esse não é um recurso do idioma C #, mas há muita discussão sobre o repositório oficial do idioma C # .

De algumas notas da reunião :

Mesmo que isso funcione em princípio, existem erros na maioria das versões do tempo de execução, para que não funcionem corretamente (nunca foram exercidos).

Precisamos de um mecanismo para entender em qual tempo de execução de destino ele trabalha. Precisamos disso para muitas coisas, e atualmente estamos analisando isso. Até lá, não podemos aguentar.

Candidato a uma versão principal do C #, se conseguirmos lidar com um número suficiente de versões de tempo de execução.

Owen Pauling
fonte
1

Minha solução alternativa é algo como isto:

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