¿Por qué .NET Framework no tiene el concepto de clases como tipos de primera clase?

20

Es bien sabido por aquellos familiarizados con la historia que C # y .NET Framework comenzaron como esencialmente "Delphi reescrito para sentirse como Java", diseñado por el desarrollador principal detrás de Delphi, Anders Hejlsberg. Las cosas han divergido bastante desde entonces, pero al principio las similitudes eran tan obvias que incluso hubo algunas especulaciones serias de que .NET en realidad era originalmente el producto de Borland.

Pero he estado mirando algunas cosas de .NET últimamente, y una de las características más interesantes y útiles de Delphi parece faltar por completo: el concepto de clases como un tipo de datos de primera clase. Para aquellos que no están familiarizados con él, el tipo TClassrepresenta una referencia a una clase, similar al Typetipo en .NET. Pero donde .NET usa Typepara la reflexión, Delphi lo usa TClasscomo una parte incorporada muy importante del lenguaje. Permite varios modismos útiles que simplemente no existen y no pueden existir sin él, como las variables de subtipo de clase y los métodos de clase virtual.

Cada lenguaje OO tiene métodos virtuales, en los que diferentes clases implementan el mismo concepto fundamental de un método de diferentes maneras, y luego se llama al método correcto en tiempo de ejecución en función del tipo real de la instancia del objeto al que se llama. Delphi extiende este concepto a las clases: si tiene una referencia TClass definida como un subtipo de clase específico (es class of TMyClassdecir, la variable puede aceptar cualquier referencia de clase que herede TMyClass, pero no cualquier cosa fuera de la jerarquía) que tenga métodos virtuales de ámbito de clase unidos a se pueden llamar sin una instancia utilizando el tipo real de la clase. La aplicación de este patrón a los constructores hace que la implementación de Factory sea trivial, por ejemplo.

No parece haber nada equivalente en .NET. Con lo útil que son las referencias de clase (y especialmente los constructores virtuales y otros métodos de clase virtual), ¿alguien ha dicho algo sobre por qué se quedaron fuera?

Ejemplos específicos

Deserialización de formularios

El Delphi VCL guarda formularios en DFMformato, un DSL para describir una jerarquía de componentes. Cuando el lector de formularios analiza los datos de DFM, se ejecuta en objetos que se describen así:

object Name: ClassName
   property = value
   property = value
   ...
   object SubObjectName: ClassName
      ...
   end
end

Lo interesante aquí es la ClassNameparte. Cada clase de componente registra su tiempo TClasscon el sistema de transmisión de componentes initialization(piense que los constructores estáticos, solo un poco diferentes, se garantiza que sucederán inmediatamente al inicio). Esto registra cada clase en una cadena-> TClass hashmap con el nombre de la clase como clave.

Cada componente desciende de TComponentque tiene un constructor virtual que toma un solo argumento, Owner: TComponent. Cualquier componente puede anular este constructor para proporcionar su propia inicialización. Cuando el lector DFM lee el nombre de una clase, busca el nombre en el hashmap mencionado anteriormente y recupera la referencia de clase correspondiente (o genera una excepción si no está allí), luego llama al constructor virtual de TComponent, que se sabe que es bueno porque la función de registro toma una referencia de clase que se requiere para descender de TComponent, y usted termina con un objeto del tipo apropiado.

Al carecer de esto, el equivalente de WinForms es ... bueno ... un gran desastre para decirlo sin rodeos, ya que requiere que cualquier nuevo lenguaje .NET vuelva a implementar completamente su propia (des) serialización. Esto es un poco impactante cuando lo piensas; Dado que el objetivo de tener un CLR es permitir que varios idiomas utilicen la misma infraestructura básica, un sistema de estilo DFM habría tenido mucho sentido.

Extensibilidad

Una clase de administrador de imágenes que escribí se puede proporcionar con una fuente de datos (como una ruta a sus archivos de imagen) y luego cargar nuevos objetos de imagen automáticamente si intenta recuperar un nombre que no está en la colección pero que está disponible en la fuente de datos. Tiene una variable de clase escrita como class ofla clase de imagen base, que representa la clase de cualquier objeto nuevo que se cree. Viene con un valor predeterminado, pero hay algunos puntos, cuando se crean nuevas imágenes con fines especiales, que las imágenes deben configurarse de diferentes maneras. (Creándolo sin un canal alfa, recuperando metadatos especiales de un archivo PNG para especificar el tamaño del sprite, etc.)

Esto podría hacerse escribiendo grandes cantidades de código de configuración y pasando opciones especiales a todos los métodos que podrían terminar creando un nuevo objeto ... o simplemente podría hacer una subclase de la clase de imagen base que anula un método virtual donde el aspecto en cuestión se configura y luego usa un bloque try / finally para reemplazar temporalmente la propiedad "clase predeterminada" según sea necesario y luego restaurarla. Hacerlo con variables de referencia de clase es mucho más simple y no es algo que podría hacerse con genéricos.

Mason Wheeler
fuente
3
¿Puede proporcionar uno o dos ejemplos concretos donde a TClasses útil, con algún código de muestra? En mi investigación superficial sobre Internet TClass, descubro que TClassse puede pasar como un parámetro. Esto se hace en .NET usando Generics. Los métodos de fábrica simplemente se marcan staticen .NET y no requieren una instancia de clase para ejecutarse.
Robert Harvey
77
@RobertHarvey: Para mí, personalmente, C # comenzó a tener mucho más sentido una vez que dejé de verlo mientras Java / C ++ se recargaba y comencé a tratarlo como Modula-2 con objetos. Es lo mismo con Java: todos dicen que fue influenciado por C ++, pero eso no es cierto. Su principal influencia es Objective-C (y Smalltalk a través de eso), y Java tendrá mucho más sentido una vez que lo trates como Smalltalk con tipos en lugar de C ++ con GC.
Jörg W Mittag
3
@RobertHarvey: TClasses una característica fundamental del lenguaje que requiere soporte del compilador. No puede "escribir el suyo" sin escribir su propio lenguaje, y para .NET incluso eso no sería suficiente porque el modelo de objetos está definido por el CLR, no por los idiomas individuales. Es algo que literalmente necesita ser parte del marco .NET en sí, o no puede existir.
Mason Wheeler
44
En primer lugar, puedo decir con la mayor certeza que .NET no fue originalmente un producto "Borland". ¿Cómo se esto? Yo era (todavía soy) parte del equipo central original que desarrolló Delphi. Trabajé de cerca con Anders, Chuck, Gary y otros. Por supuesto, estoy seguro de que lo sabes. En cuanto a la existencia de una referencia de clase (como se llama TClass y construcciones similares) en .NET, probablemente se consideró innecesario debido a la existencia de una rica información de tipo accesible en tiempo de ejecución. Al principio, Delphi tenía mucha menos información de tipo y las referencias de clase eran un compromiso.
Allen Bauer

Respuestas:

5

.NET (CLR) fue la tercera generación del Modelo de objetos componentes (COM) de Microsoft, que en los primeros días se denominaba "tiempo de ejecución COM +". Microsoft Visual Basic y el mercado de controles COM / ActiveX ha tenido mucha más influencia en las opciones específicas de compatibilidad arquitectónica CLR que Borland Delphi. (Es cierto que el hecho de que Delphi haya adoptado los controles ActiveX ciertamente ayudó a hacer crecer el ecosistema ActiveX, pero COM / ActiveX existía antes de Delphi)

La arquitectura de COM se desarrolló en C (no en C ++) y se centró en las interfaces, en lugar de las clases. Además, admite la composición de objetos, lo que significa que un objeto COM podría estar compuesto de varios objetos diferentes, con la interfaz IUnknown uniéndolos. Pero IUnknown no tuvo ningún papel en la creación de objetos COM, que fue diseñado para ser lo más independiente posible del lenguaje. La creación de objetos fue manejada generalmente por IClassFactory, y la reflexión por ITypeLibrary y las interfaces relacionadas. Esta separación de preocupaciones fue independiente del lenguaje de implementación, y las características de cada interfaz COM básica se mantuvieron mínimas y ortogonales.

Entonces, como resultado de la popularidad de los controles COM y ActiveX, la arquitectura .NET se creó para admitir COM IUnknown, IClassFactory e ITypeLibrary. En COM, estas interfaces no estaban necesariamente en el mismo objeto, por lo que agruparlas no necesariamente tenía sentido.

Burt_Harris
fuente