O que exatamente é uma “Classe Especial”?

114

Depois de não conseguir obter algo como o seguinte para compilar:

public class Gen<T> where T : System.Array
{
}

com o erro

Uma restrição não pode ser uma classe especial `System.Array '

Comecei a me perguntar, o que exatamente é uma "aula especial"?

Muitas vezes as pessoas parecem obter o mesmo tipo de erro quando especificam System.Enumem uma restrição genérica. Eu tenho os mesmos resultados com System.Object, System.Delegate, System.MulticastDelegatee System.ValueTypetambém.

Existem mais deles? Não consigo encontrar nenhuma informação sobre "classes especiais" em C #.

Além disso, o que há de tão especial nessas classes que não podemos usá-las como uma restrição de tipo genérico?

Mints97
fonte
14
Não acho que seja uma cópia direta. A questão não é "por que não posso usar isso como uma restrição", é "o que são essas classes especiais". Dei uma olhada nessas perguntas e elas apenas afirmam por que seria inútil usá-la como uma restrição, sem explicar o que uma "classe especial" realmente é e por que ela é considerada especial.
Adam Houldsworth
2
Na minha experiência, classes que são usadas, mas você não pode usá-las diretamente, apenas implicitamente por meio de outra sintaxe, são classes especiais. Enum se enquadra na mesma categoria. O que exatamente os torna especiais, eu não sei.
Lasse V. Karlsen
@AndyKorneyev: essa questão é um pouco diferente. Estou pedindo uma definição de "classe especial" e / ou uma lista abrangente delas. Essa questão simplesmente pergunta a razão pela qual System.Array não pode ser uma restrição de tipo genérico.
Mints97
A partir da documentação , afirma "[...] apenas o sistema e os compiladores podem derivar explicitamente da classe Array.". É provável que seja isso o que a torna uma classe especial - ela é tratada de maneira especial pelo compilador.
RB.
1
@RB .: errado. Essa lógica significaria que nãoSystem.Object é uma "classe especial", pois isso é válido:, mas ainda é uma "classe especial". public class X : System.Object { }System.Object
Mints97

Respostas:

106

Do código-fonte Roslyn, parece uma lista de tipos codificados:

switch (type.SpecialType)
{
    case SpecialType.System_Object:
    case SpecialType.System_ValueType:
    case SpecialType.System_Enum:
    case SpecialType.System_Delegate:
    case SpecialType.System_MulticastDelegate:
    case SpecialType.System_Array:
        // "Constraint cannot be special class '{0}'"
        Error(diagnostics, ErrorCode.ERR_SpecialTypeAsBound, syntax, type);
        return false;
}

Fonte: Binder_Constraints.cs IsValidConstraintType
Encontrei usando uma pesquisa do GitHub: "Uma restrição não pode ser uma classe especial"

Kobi
fonte
1
@kobi 702 torna-se o erro do compilador CS0702, conforme visto na saída do compilador (que esta questão não citou) e outras respostas.
AakashM
1
@AakashM - Obrigado! Tentei compilar e não obtive o número do erro, por algum motivo. Em seguida, levei quase 5 minutos para descobrir e não tive tempo suficiente para editar meu comentário. Triste história.
Kobi
1
@Kobi: você tem que olhar para a janela de saída , onde você encontra o número exato do código de erro do compilador CS0702.
Tim Schmelter
9
Portanto, agora a verdadeira questão é por que essas classes especiais?
David diz Restabelecer Monica em
@DavidGrinberg Talvez a razão seja que você não pode herdar desses tipos diretamente (exceto para object), ou pelo menos tem algo a ver com isso. Também where T : Arraypermitiria passar Ensaio como T, o que provavelmente não é o que a maioria das pessoas deseja.
IllidanS4 quer Monica de volta em
42

Encontrei um comentário de Jon Skeet de 2008 sobre uma questão semelhante: Por que a System.Enumrestrição não é suportada.

Eu sei que isso é um pouco fora do assunto , mas ele perguntou a Eric Lippert (a equipe C #) sobre isso e eles deram esta resposta:

Primeiro, sua conjectura está correta; as restrições às restrições são em grande parte artefatos da linguagem, não tanto do CLR. (Se fizéssemos esses recursos, haveria algumas pequenas coisas que gostaríamos de mudar no CLR sobre como os tipos enumeráveis ​​são especificados, mas principalmente isso seria trabalho de linguagem.)

Em segundo lugar, eu pessoalmente adoraria ter restrições de delegação, restrições de enum e a capacidade de especificar restrições que são ilegais hoje porque o compilador está tentando salvá-lo de você mesmo. (Ou seja, tornar os tipos selados legais como restrições e assim por diante.)

No entanto, devido a restrições de programação, provavelmente não conseguiremos incluir esses recursos na próxima versão do idioma.

Amir Popovich
fonte
10
@YuvalItzchakov - Citar Github \ MSDN é melhor? A equipe C # deu uma resposta concreta sobre o problema ou algo semelhante. Não pode machucar ninguém. Jon Skeet acabou de citá-los e é bastante confiável quando chega a C # ..
Amir Popovich
5
Não precisa ficar chateado. Eu não quis dizer que esta não é uma resposta válida :) Estava apenas compartilhando meus pensamentos sobre a fundação que é jonskeet; p
Yuval Itzchakov
40
Para sua informação, acho que é a mim que você está citando. :-)
Eric Lippert
2
@EricLippert - Isso torna a cotação ainda mais confiável.
Amir Popovich
O domínio do link em resposta está morto.
Pang
25

De acordo com o MSDN , é uma lista estática de classes:

Erro do compilador CS0702

A restrição não pode ser um 'identificador' de classe especial. Os seguintes tipos não podem ser usados ​​como restrições:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Tim Schmelter
fonte
4
Legal, parece a resposta certa, bom achado! Mas onde está System.MulticastDelegatena lista?
Mints97
8
@ Mints97: não faço ideia, talvez falta de documentação?
Tim Schmelter
Parece que você também não pode herdar dessas classes.
David Klempfner
14

De acordo com a especificação de linguagem C # 4.0 (codificado: [10.1.5] restrições de parâmetro de tipo) diz duas coisas:

1] O tipo não deve ser objeto. Como todos os tipos derivam de objeto, tal restrição não teria efeito se fosse permitida.

2] Se T não tem restrições primárias ou restrições de parâmetro de tipo, sua classe base efetiva é objeto.

Ao definir uma classe genérica, você pode aplicar restrições aos tipos de tipos que o código do cliente pode usar para argumentos de tipo ao instanciar sua classe. Se o código do cliente tentar instanciar sua classe usando um tipo que não é permitido por uma restrição, o resultado será um erro em tempo de compilação. Essas restrições são chamadas de restrições. As restrições são especificadas usando a palavra-chave contextual where. Se você deseja restringir um tipo genérico a um tipo de referência, use: class.

public class Gen<T> where T : class
{
}

Isso proibirá que o tipo genérico seja um tipo de valor, como int ou uma estrutura etc.

Além disso, a restrição não pode ser um 'identificador' de classe especial. Os seguintes tipos não podem ser usados ​​como restrições:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
fonte
12

Existem certas classes na Estrutura que efetivamente transmitem características especiais para todos os tipos derivados delas, mas não possuem essas características . O próprio CLR não impõe nenhuma proibição contra o uso dessas classes como restrições, mas os tipos genéricos restritos a elas não adquiririam as características não herdadas da mesma forma que os tipos concretos. Os criadores do C # decidiram que, como esse comportamento pode confundir algumas pessoas, e eles deixaram de ver qualquer utilidade nele, eles deveriam proibir essas restrições em vez de permitir que se comportassem como o fazem no CLR.

Se, por exemplo, alguém pudesse escrever void CopyArray<T>(T dest, T source, int start, int count):; seria possível passar deste sourcepara métodos que esperam um argumento do tipo System.Array; além disso, seria possível obter a validação em tempo de compilação de que deste sourceeram os tipos de array compatíveis, mas não seria capaz de acessar elementos do array usando o []operador.

A incapacidade de usar Arraycomo uma restrição é muito fácil de contornar, uma vez void CopyArray<T>(T[] dest, T[] source, int start, int count)que funcionará em quase todas as situações em que o método anterior funcionaria. No entanto, ele tem uma fraqueza: o método anterior funcionaria no cenário em que um ou ambos os argumentos fossem do tipo, System.Arrayao mesmo tempo que rejeitava casos em que os argumentos eram tipos de array incompatíveis; adicionar uma sobrecarga onde ambos os argumentos fossem do tipo System.Arrayfaria o código aceitar os casos adicionais que deveria aceitar, mas também faria com que ele aceitasse erroneamente os casos que não deveria.

Acho a decisão de proibir a maioria das restrições especiais cansativa. O único que teria significado semântico zero seria System.Object[já que se isso fosse legal como uma restrição, qualquer coisa iria satisfazê-lo]. System.ValueTypeprovavelmente não seria muito útil, uma vez que as referências de tipo ValueTypenão têm muito em comum com os tipos de valor, mas pode plausivelmente ter algum valor em casos envolvendo Reflexão. Ambos System.Enume System.Delegateteriam alguns usos reais, mas como os criadores do C # não pensaram neles, eles são proibidos sem um bom motivo.

supergato
fonte
10

O seguinte pode ser encontrado no CLR via C # 4ª edição:

Restrições Primárias

Um parâmetro de tipo pode especificar nenhuma restrição primária ou uma restrição primária. Uma restrição primária pode ser um tipo de referência que identifica uma classe que não é selada. Você não pode especificar um dos seguintes tipos de referência especial: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum ou System.Void . Ao especificar uma restrição de tipo de referência, você está prometendo ao compilador que um argumento de tipo especificado será do mesmo tipo ou de um tipo derivado do tipo de restrição.

Claudio P
fonte
Veja também: C seção # LS 10.1.4.1: A classe base direta de um tipo de classe não deve ser qualquer um dos seguintes tipos: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, ou System.ValueType. Além disso, uma declaração de classe genérica não pode ser usada System.Attributecomo uma classe base direta ou indireta.
Jeroen Vannevel
5

Não creio que exista qualquer definição oficial de "classes especiais" / "tipos especiais".

Você pode pensar neles como tipos, que não podem ser usados ​​com a semântica de tipos "regulares":

  • você não pode instanciá-los diretamente;
  • você não pode herdar o tipo personalizado deles diretamente;
  • há alguma mágica do compilador para trabalhar com eles (opcionalmente);
  • o uso direto de suas instâncias pelo menos inúteis (opcionalmente; imagine, que você criou genérico acima, que código genérico você vai escrever?)

PS Eu adicionaria System.Voidà lista.

Dennis
fonte
2
System.Voiddá um erro totalmente diferente quando usado como uma restrição genérica =)
Mints97
@ Mints97: verdadeiro. Mas se a pergunta é sobre "especial", então sim, voidé muito especial. :)
Dennis
@Dennis: O código que possui alguns parâmetros de um tipo restrito System.Arraypode usar métodos como Array.Copymover dados de um para o outro; o código com parâmetros de um tipo restrito a System.Delegateseria capaz de usar Delegate.Combineneles e converter o resultado para o tipo adequado . Fazer uso efetivo de um tipo conhecido genérico Enumusará o Reflection uma vez para cada tipo, mas um HasAnyFlagmétodo genérico pode ser 10 vezes mais rápido do que um método não genérico.
supercat