¿Qué es exactamente una "clase especial"?

114

Después de no poder compilar algo como lo siguiente:

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

con el error

Una restricción no puede ser una clase especial `System.Array '

Empecé a preguntarme, ¿qué es exactamente una "clase especial"?

Las personas a menudo parecen tener el mismo tipo de error cuando especifican System.Enumen una restricción genérica. Me dieron los mismos resultados con System.Object, System.Delegate, System.MulticastDelegatey System.ValueTypetambién.

¿Hay más de ellos? No puedo encontrar información sobre "clases especiales" en C #.

Además, ¿qué tienen de especial esas clases que no podemos usarlas como una restricción de tipo genérico?

Mentas97
fuente
14
No creo que esto sea un duplicado directo. La pregunta no es "por qué no puedo usar esto como una restricción", es "cuáles son estas clases especiales". He echado un vistazo a esas preguntas y solo dicen por qué sería inútil usarlas como restricción, sin explicar qué es realmente una "clase especial" y por qué se considera especial.
Adam Houldsworth
2
En mi experiencia, las clases que se usan pero no se pueden usar directamente, solo implícitamente a través de otra sintaxis, son clases especiales. Enum entra en la misma categoría. Qué los hace especiales exactamente, no lo sé.
Lasse V. Karlsen
@AndyKorneyev: esa pregunta es algo diferente. Estoy solicitando una definición de "clase especial" y / o una lista completa de estas. Esa pregunta simplemente pregunta por la razón System.Array no puede ser una restricción de tipo genérico.
Mints97
De la documentación dice "[...] sólo el sistema y los compiladores pueden derivar explícitamente de la clase Array". Es probable que esto sea lo que la convierte en una clase especial: el compilador la trata especialmente.
RB.
1
@RB .: mal. Esta lógica significaría System.Objectque no es una "clase especial", ya que esto es válido:, public class X : System.Object { }pero System.Objectsigue siendo una "clase especial".
Mints97

Respuestas:

106

Desde el código fuente de Roslyn, parece una 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;
}

Fuente: Binder_Constraints.cs IsValidConstraintType
Lo encontré usando una búsqueda de GitHub: "Una restricción no puede ser una clase especial"

Kobi
fuente
1
@kobi 702 se convierte en el error del compilador CS0702, como se ve en la salida del compilador (que esta pregunta omitió citar) y otras respuestas.
AakashM
1
@AakashM - ¡Gracias! Intenté compilar y no obtuve el número de error, por alguna razón. Luego me tomó casi 5 minutos averiguarlo y no tuve tiempo suficiente para editar mi comentario. Historia triste.
Kobi
1
@Kobi: tienes que mirar la ventana de salida , allí encontrarás el número exacto del código de error del compilador CS0702.
Tim Schmelter
9
Entonces, ahora la verdadera pregunta es ¿por qué son estas clases especiales?
David dice Reincorporar a Monica
@DavidGrinberg Quizás la razón es que no puedes heredar directamente de estos tipos (excepto object), o al menos tiene algo que ver con eso. También where T : Arraypermitiría pasar el ensayo como T, que probablemente no es lo que la mayoría de la gente quiere.
IllidanS4 quiere que Monica vuelva
42

Encontré un comentario de Jon Skeet de 2008 sobre una pregunta similar: ¿Por qué la System.Enumrestricción no admite ?

Sé que esto está un poco fuera de tema , pero le preguntó a Eric Lippert (el equipo de C #) al respecto y proporcionaron esta respuesta:

En primer lugar, su conjetura es correcta; las restricciones sobre las restricciones son en general artefactos del lenguaje, no tanto del CLR. (Si tuviéramos que hacer estas funciones, habría algunas cosas menores que nos gustaría cambiar en el CLR con respecto a cómo se especifican los tipos enumerables, pero principalmente esto sería trabajo de lenguaje).

En segundo lugar, personalmente me encantaría tener restricciones de delegado, restricciones de enumeración y la capacidad de especificar restricciones que son ilegales hoy porque el compilador está tratando de salvarlo de usted mismo. (Es decir, legalizar los tipos sellados como restricciones, etc.)

Sin embargo, debido a las restricciones de programación, es probable que no podamos incluir estas funciones en la próxima versión del idioma.

Amir Popovich
fuente
10
@YuvalItzchakov - ¿Es mejor citar Github \ MSDN? El equipo de C # ha dado una respuesta concreta sobre el problema o uno similar. Realmente no puede lastimar a nadie. Jon Skeet acaba de citarlos y es bastante confiable cuando llega a C # ..
Amir Popovich
5
No hay necesidad de enojarse. No quise decir que esta no es una respuesta válida :) Solo estaba compartiendo mis pensamientos sobre la fundación que es jonskeet; p
Yuval Itzchakov
40
Para su información, por cierto, creo que soy yo quien está citando allí. :-)
Eric Lippert
2
@EricLippert: eso hace que la cotización sea aún más confiable.
Amir Popovich
El dominio del enlace en respuesta está muerto.
Pang
25

Según MSDN , es una lista estática de clases:

Error del compilador CS0702

La restricción no puede ser un 'identificador' de clase especial Los siguientes tipos no se pueden utilizar como restricciones:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Tim Schmelter
fuente
4
Genial, parece la respuesta correcta, ¡buen descubrimiento! Pero, ¿dónde está System.MulticastDelegateen la lista?
Mints97
8
@ Mints97: ni idea, ¿tal vez falta de documentación?
Tim Schmelter
Parece que tampoco puedes heredar de estas clases.
David Klempfner
14

Según la especificación del lenguaje C # 4.0 (codificado: [10.1.5] restricciones de parámetro de tipo) dice dos cosas:

1] El tipo no debe ser objeto. Debido a que todos los tipos derivan del objeto, tal restricción no tendría ningún efecto si se permitiera.

2] Si T no tiene restricciones primarias o restricciones de parámetro de tipo, su clase base efectiva es objeto.

Cuando define una clase genérica, puede aplicar restricciones a los tipos de tipos que el código de cliente puede usar para argumentos de tipo cuando crea una instancia de su clase. Si el código del cliente intenta crear una instancia de su clase utilizando un tipo que no está permitido por una restricción, el resultado es un error en tiempo de compilación. Estas restricciones se denominan restricciones. Las restricciones se especifican mediante el uso de la palabra clave contextual where. Si desea restringir un tipo genérico para que sea un tipo de referencia, use: class.

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

Esto prohibirá que el tipo genérico sea un tipo de valor, como int o una estructura, etc.

Además, Restricción no puede ser un 'identificador' de clase especial. Los siguientes tipos no pueden usarse como restricciones:

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

Hay ciertas clases en el Marco que efectivamente transmiten características especiales a todos los tipos derivados de ellas, pero no poseen esas características en sí mismas . El CLR en sí mismo no impone ninguna prohibición contra el uso de esas clases como restricciones, pero los tipos genéricos restringidos a ellas no adquirirían las características no heredadas como lo harían los tipos concretos. Los creadores de C # decidieron que debido a que tal comportamiento podría confundir a algunas personas y no vieron ninguna utilidad en él, deberían prohibir tales restricciones en lugar de permitirles comportarse como lo hacen en CLR.

Si, por ejemplo, a uno se le permitiera escribir void CopyArray<T>(T dest, T source, int start, int count):; uno podría pasar destya sourcemétodos que esperan un argumento de tipo System.Array; Además, se obtendría la validación en tiempo de compilación de que desty sourceeran los tipos de matriz compatibles, pero no se podría acceder a los elementos de la matriz utilizando el[] operador.

La incapacidad de usar Arraycomo restricción es bastante fácil de evitar, ya void CopyArray<T>(T[] dest, T[] source, int start, int count)que funcionará en casi todas las situaciones en las que funcionaría el método anterior. Sin embargo, tiene una debilidad: el método anterior funcionaría en el escenario en el que uno o ambos argumentos fueran de tipo System.Arraymientras se rechazan los casos en los que los argumentos son tipos de matrices incompatibles; agregar una sobrecarga donde ambos argumentos fueran de tipo System.Arrayharía que el código aceptara los casos adicionales que debería aceptar, pero también haría que aceptara erróneamente los casos que no debería.

Encuentro molesta la decisión de prohibir la mayoría de las restricciones especiales. El único que tendría un significado semántico cero sería System.Object[ya que si eso fuera legal como restricción, cualquier cosa lo satisfaría]. System.ValueTypeprobablemente no sería muy útil, ya que las referencias de tipo ValueTypeno tienen mucho en común con los tipos de valor, pero podría tener algún valor plausiblemente en casos que involucren Reflection. Ambos System.Enumy System.Delegatetendrían algunos usos reales, pero como los creadores de C # no pensaron en ellos, están prohibidos sin una buena razón.

Super gato
fuente
10

Lo siguiente se puede encontrar en CLR a través de C # 4th Edition:

Restricciones primarias

Un parámetro de tipo puede especificar cero restricciones primarias o una restricción primaria. Una restricción primaria puede ser un tipo de referencia que identifica una clase que no está sellada. No puede especificar uno de los siguientes tipos de referencia especiales: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum o System.Void . Al especificar una restricción de tipo de referencia, le promete al compilador que un argumento de tipo especificado será del mismo tipo o de un tipo derivado del tipo de restricción.

Claudio P
fuente
Ver también: C # LS sección 10.1.4.1: La clase base directa de un tipo de clase no debe ser cualquiera de los siguientes tipos: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, o System.ValueType. Además, una declaración de clase genérica no se puede utilizar System.Attributecomo clase base directa o indirecta.
Jeroen Vannevel
5

No creo que exista una definición oficial de "clases especiales" / "tipos especiales".

Puede pensar en ellos como tipos, que no se pueden usar con semánticas de tipos "regulares":

  • no puede instanciarlos directamente;
  • no puede heredar el tipo personalizado de ellos directamente;
  • hay algo de magia del compilador para trabajar con ellos (opcionalmente);
  • el uso directo de sus instancias al menos inútil (opcionalmente; imagina, que has creado genérico arriba, ¿qué código genérico vas a escribir?)

PD: lo agregaría System.Voida la lista.

Dennis
fuente
2
System.Voidda un error completamente diferente cuando se usa como una restricción genérica =)
Mints97
@ Mints97: cierto. Pero si la pregunta es sobre "especial", entonces sí, voides muy especial. :)
Dennis
@Dennis: el código que tiene un par de parámetros de un tipo restringido System.Arraypodría usar métodos como Array.Copymover datos de uno a otro; el código con parámetros de un tipo restringido System.Delegatepodría usarse Delegate.Combineen ellos y emitir el resultado al tipo adecuado . Hacer un uso efectivo de un tipo genérico conocido Enumserá usar Reflection una vez para cada tipo, pero un HasAnyFlagmétodo genérico puede ser 10 veces más rápido que un método no genérico.
supercat