Hay cosas del C# que no puedo comprender…

… y que rompe por completo las reglas de todo lenguaje de programación, de toda lógica y lo que es peor, del mismo concepto de orientación a objetos. Por lo menos así lo creo, y me gustaría estar equivocado (y que alguien me lo demostrara).

El primer tema es el hecho de que los tipos básicos, las enumeraciones, las estructuras y los delegados hereden automática y ocultamente de clases .NET. Y la palabra clave es «ocultamente». ¿Por qué? Entiendo el tema del «encajado» y «desencajado», pero no puedo entender esa ocultación. Como en la teoría cuántica, hay elementos que son objetos y que no lo son, y de la misma forma que en la teoría cuántica la medición de un objeto lo colapsa, así ocurre con el C#: cuando miramos un elemento de los citados, o es un objeto o un elemento básico, con la sobrecarga que eso conlleva: si antes era lo contrario, hay que convertirlo al nuevo valor, con pérdida de tiempo y de recursos.

Pero también ocurren otras cosas un tanto extrañas. Veámoslo con un ejemplo. Una enumeración hereda de System.Enum, pero sin embargo dicho System.Enum no es una enumeración, sino una clase. Y justo al revés, una enumeración es una enumeración (en el concepto tradicional) que podemos convertir en objeto y que así pase a ser algo alojado en el montículo y que tiene los miembros de System.Enum. Y yo me digo, ¿qué más da que esté en la pila o en el montículo? Es un objeto y ya está. O no lo es. ¿Por qué cuando está en la pila no podemos usar sus métodos miembros y sí cuando está en el montículo? Y lo más incomprensible para mí: cada vez que usamos una enumeración como tal, la tenemos en la pila, y cuando la usamos como un objeto, debemos pasarla al montículo, y de nuevo al revés. Me vais a perdonar pero no lo entiendo.

Lo que veo (y me gustaría no ver) es lo que considero una serie de errores de concepto garrafales en todo el .NET; la propia idiosincrasia del .NET está mal, al menos en los aspectos que cito. ¿Por qué toda esa manía de que todo sea un objeto cuando evidentemente muchas cosas no lo son? Repito que es una apreciación personal, pero de momento es la que tengo. Y se va confirmando conforme voy leyendo el libro de Hejlsberg.

Otro tema que me trae de cabeza son los destructores. ¿Los hay? Según Hejlsberg sí, pero resulta que se implementan mediante la sobreescritura del método Finalize de la clase System.Object. Pero entonces no son destructores, sino algo que el sistema en tiempo de ejecución llama cuando el recolector de basura así lo decide. Al final, en las pocas páginas en las que se trata el tema (352-354) no queda nada claro, pero nada de nada la finalidad y el objeto de los destructores, así como el uso de Finalize. Y tampoco menciona qué pasa con Dispose. La verdad es que parece que ni el propio creador del lenguaje se entera. ¿Cómo vamos, pues, a enterarnos los mortales comunes?

Todavía queda una cosa más: la palabra reservada extern en constructores, destructores y demás elementos de una clase. Parece ser que son elementos que no tienen implementación. Pero no se dan más explicaciones. Ni dónde se podría encontrar, ni cómo, ni para qué sirven. En otros lenguajes con extern indicamos que dicho elemento está implementado en otro lugar, pero que dicha implementación existe en algún lugar. Hejlsberg no nos dice nada sobre ello. Pero aquí hay truco, aunque venga después. No sé si el libro lo documenta, pero espero que sí. Dejamos al lector que se imagine para qué puede servir dicho extern, pero diremos que desde mi punto de vista es otra de esas «cosas raras» que no me gustan un pelo del C# y por extensión del .NET

Me vais a perdonar la expresión, pero considero que por estos aspectos (y alguno más que ya he mencionado con anterioridad) el .NET y el C# (y por supuesto el VB.NET y el C++/CLI que es CLI) es un aborto, carente de mucha lógica y consistencia.

14 comentarios sobre “Hay cosas del C# que no puedo comprender…”

  1. Hola, Rafa!!!

    Ahí van mis opiniones y señalamientos:

    a) «Una enumeración hereda de System.Enum, pero sin embargo dicho System.Enum no es una enumeración, sino una clase.»

    El deseo de englobar todo bajo una jerarquía uniforme q se rige por los mismos principios a veces obliga a hacer complejos juegos malabares. System.Enum en sí misma *NO* es una enumeración, sino solamente el tipo (yo prefiero decir «el tipo», aunque en la propia documentación de MS se dice «una clase») del que heredan todas las enumeraciones definidas por el programador. ¿Para qué? Pienso q principalmente para que al usar Refection podamos preguntar si un tipo es una enumeración diciendo if (x is Enum)… Hereda de System.ValueType, y por ello todas las enumeraciones son tipos valor y se almacenan por defecto en el stack.

    b) Y yo me digo, ¿qué más da que esté en la pila o en el montículo?

    EFICIENCIA es la palabra. Sobre eso tú nos puedes enseñar un montón a casi todos aquí.

    c) ¿Por qué cuando está en la pila no podemos usar sus métodos miembros y sí cuando está en el montículo?

    Cuando yo como programador declaro Color c; donde ‘c’ es un enum, puedo hacer c.ToString() cada vez que se me antoje. Sin preocuparme de nada más, solo usando la sintaxis normal de objetos. Que el sistema internamente haga un «boxing» o lo que le dé la gana es en el fondo un detalle de implementación, creo.

    d) ¿Por qué toda esa manía de que todo sea un objeto cuando evidentemente muchas cosas no lo son?

    Uniformidad. Sea del tipo q sea tu variable, podrás aplicarle los conceptos OO de propiedades, métodos y eventos. Es un mecanismo orientado a elevar el nivel conceptual de programación; a alejarnos más «del hierro» y permitirnos pensar en términos de más alto nivel. Ya se encargará el compilador de traducir nuestras sentencias en código lo más eficiente posible.

    >> Otro tema que me trae de cabeza son los destructores.

    Los finalizadores (el uso de la palabra «destructor» solo trae confusión a los que vienen de C++) son imprescindibles para garantizar que nuestros objetos no dejen «m…» tras de sí cuando dejan de ser referenciados. Porque no podemos estar seguros, por ejemplo, de que todo programador q cree un objeto de una clase nuestra q consume recursos no manejados llamará al método Dispose() para liberarlos cuando termine de usar el objeto. En ese sentido, el finalizador es quien garantiza en última instancia la «limpieza» del sistema. Existe un patrón bien definido para combinar de manera armónica Dispose() y Finalize().

    >> La verdad es que parece que ni el propio creador del lenguaje se entera.

    !!!!!

    >> extern… Parece ser que son elementos que no tienen implementación. Pero no se dan más explicaciones.

    Precisamente porque se corresponden con funciones (hablando en términos de C) escritas en otros lenguajes. Ten en cuenta q .NET ha sido diseñado para trabajar sobre múltiples plataformas… Los detalles del «mapeado» del método extern en una función de la plataforma de destino se indican (a mi modo de ver, muy correctamente) a través de atributos, lo que deja la puerta abierta para definir nuevos convenios, etc. en caso de necesidad).

    Saludos – Octavio

  2. Hola Rafael,

    Solo comentarte que existen otros libros que puedan ayudarte en el cambio del C al CS. Personalmente te recomiendo este que a pesar de ser de los primeros en salir al mercado, quizas seas lo que estas buscando porque se realizan continuas alusiones a la programación en C

    «A Fondo C#» de Tom Archer – McGraw-Hill
    http://www.amazon.com/Inside-C-Second-Tom-Archer/dp/0735616485/ref=pd_bbs_sr_1/002-5657571-7050469?ie=UTF8&s=books&qid=1176048037&sr=8-1

    Saludos.

  3. Evelardiez, no se trata del cambio, tengo a cuestas unso cuantos programas en C#.

    PabloNetrix, a veces yo mismo me pregunto eso, y desde luego según MS C# SÍ pretende ser un C++ 2.0, ya que es el lenguage en el que dentro de unos años (en teoría para el Vista, en la práctica todavía no) debería ser un sustituto del C++ para crear aplicaciones de todo tipo… De hecho tienes el MicroFramework, que es una plataforma para micros tiny de 32 bits con la que puedes hacer drivers y controlar hardware directamente, digamos que es un preview para dentro de unos años cuando los micros sean capaces de ejecutar MSIL directamente.

    Octavio, como siempre, genial. Me aclaras algunas cosas, pero no del todo. Tu punto b confirma lo que digo: si está en la pila, lo pasas al montículo y luego a la pila… no es que haya mucha eficiencia que digamos, sobre todo si trasteamos con arrays grandes etc… Además, que un objeto esté en el montículo operado a través de una referencia apenas tiene pérdida de rendimiento: como mucho es una indirección más, con una herramienta optimizadora ni siguiera eso: un valor guardado en un registro del micro con direccionamiento indirecto. Lo que me joden son esos movimientos.

    Sobre el tema de los destructores y mi comentario de que parece que ni Hejlsberg se entera es que el propio Hejlsberg los llama destructores y no explica ni profundiza nada sobre el tema (mira el libro en las páginas que comento).

    Y finalmente el tema del extern, efectivamente es para eso y para el interop, pero no me mola. Lo que sí que me molan son los atributos, y creo que será mi siguiente entrada aquí.

    🙂

  4. Acerca de la pila y el monticulo:

    El lugar en donde en principio residen las instancias de los tipos por valor (estructuras, enums, ints, etc…) es la pila. El lugar donde en principio residen las instancias de los tipos por referencia es el monticulo. Efectivamente, a traves de boxing y unboxing se puede hacer que un tipo por valor resida en el montículo. Y hay ocasiones que no hay mas pelotas. Por ejemplo, cuando le pasas como parámetro un point (que es una estructura) a un método que requiere un object, hay un boxing. Hay una pérdida de eficiencia en pro de universalidad y transparencia. Pero es algo de lo que hay que ser consciente.

    Sin embargo, puedes llamar los métodos y los miembros de un tipo por valor (por ejemplo, Point) mientras reside en la pila sin ningún problema. Cosa diferente es un enum. Existen restricciones al tipo de base de un enum. No te lo voy a asegurar porque no me lo se de memoria, pero creo recordar que eran System.Int32. Y por lo tanto, los métodos que puedes llamar sobre un enum están restringidos.

    C# no es C++ 2.0, .NET Micro edition no permite acceso irrestricto al hardware (a la C++). Sino acceso a través de interfaces bien definidas.

    Lee el libro «CLR via C#», en el caso de que no lo hayas hecho. Ves las tripas al descubierto.

  5. Augusto, la cuestión no es esa, el tema es la dualidad onda-corpúsculo, digoooo, la dualidad entre ente y objeto que presenta un tipo básico y que puede tener serias penalizaciones de rendimiento, aparte del hecho de que esa dualidad presenta elementos de «código oculto» que IMHO no debería existir en un lenguaje de verdad.

    Tio Luiso, si sigo con el C# y el .NET (que no tengo claro, no por que no me guste, sino por requerimientos de mi empresa), es uno de los libros que tengo previstos estudiar, aparte de que haré pruebas e intentaré poner en un brete al compilador de C#… Pero está previsto para más adelante.

    Respecto a las enumeraciones, el tema es todavía más «cuántico»: heredan de System.Enum y de System., es decir, puedes tener una enumeración que sea byte, sbyte, short, etc.. Aquí tenemos una triple dualidad.

    Desde mi punto de vista podría haber sido mucho más claro: todo son objetos que pueden ir en la pila o en el montículo (o, como hace el C++ Builder y el Delphi, los elementos que tengan que ver con ventanas sólo pueden ir en el montículo -por motivos de doble instanciación, clase/elemento visual). Y el usuario decide, como el caso del C++/CLI (aunque aquí el compilador nos engaña, todo es igual que en C# pero en apariencia no).

    Personalmente lo veo demasiado artificial, demasiado cogido del pelo y arrastrado de acá para allá.

  6. Hola Rafa,

    Sí que he entendido la cuestión, sólo me he limitado a apuntar a anónimoatacante en la dirección correcta de un concepto que es erróneo (el tema de que sean diferentes System.Int32 e int, indicando que uno es una estructura y el otro una clase).

    Bajo mi punto de vista, sobre el tema que tratas, es cierto que las enumeraciones están definidas de forma un poco extraña, pero la flexibilidad que nos otorgan compensa bastante su definición. El hecho de poder hacer parsing de cadenas, por poner un ejemplo, para obtener el valor enumerado asociado, hace que la serialización de valores enumerados se pueda hacer en un pispás, y el objeto serializado (por ejemplo en XML) es más entendible para un humano – aunque todos sepamos que el fin de la serialización es el uso por parte de las máquinas.

    De todas formas, coincido con Octavio, el hecho de poder tratar un Enum (o una estructura) de la misma forma que se tratan todos los objetos en el lenguaje hace que sea más sencillo su uso. Si no, tendríamos que aprender a usar por un lado los tipos básicos (donde los enumerados no serían más que «alias» para valores, al estilo de los #define 😉 ), por otro lado las estructuras, y por otro lado los objetos. Creo que la simplicidad de uso compensa la complejidad que pueda tener el código generado.

  7. Sí que hay ciertas cuestiones «oscuras». Ahora mismo no tengo EL LIBRO (CLR via C#) delante, de modo que igual digo alguna animalada, pero si no recuerdo mal, los tipos por valor en definitiva también están en la jerarquía de clases. Creo recordar que todos heredan de forma DIRECTA de MarshalByValueObject (no se pueden especificar ninguna estructura diferente para heredar de ella). Por lo que… ¿Int32 hereda de Object? Pues sí. En definitiva, acaba heredando de object.

    Al final, se trata de un compromiso en complejidad / entendibilidad / universalidad. ¿Que hay trampitas? Obviamente.

    Es una decisión de diseño discutible (como lo son casi todas), pero me parece asumible. Una vez que comprendes BIEN lo que está ocurriendo en las tripas, es simple.

    Por ejemplo, me parece más discutible la decisión de no estar obligado a declarar (como en Java) las excepciones que puede lanzar un método.

    Con todo y con eso, .NET en general, y C# en particular, me parece que ofrecen un conjunto muy equilibrado de funcionalidades que si se comprenden ofrecen una potencia increíble sin comprometer la seguridad del código (gracias a que NO existen los punteros de C++, que son su mayor virtud y su mayor defecto)

  8. Vamos a ver, vamos a ver…

    Ventaja/desventaja entre el stack y el heap: piensa en el GC, de manera que no da igual.

    Un enum es una clase o un int?. El compilador crea una clase que hereda de Enum con cada elemento definido como un campo constante de tipo básico numérico excepto char (int si no le especificas nada). Al usar los elementos del enum, usas un número constante (normalmente stack), al usar el enum en sí, usas una clase (heap), se ve bien con una prueba sencilla con el ildasm.

    En el caso de los destructores, hay que usarlos con cuidado, y tiene relación de nuevo con el GC. Si no los usas no penalizas nada, pero si los usas, tienes un nuevo thread que tiene que trabajar y una nueva espera de ciclo de GC para liberar memoria. ¿Cuándo usarlos? Pues tú mismo, hay que evaluar si viene bien o no, y combinarlo como dice Octavio con Dispose si te viene Dispose mejor, o mejor aún, hacer código que no lo necesite 😉

    Por último, la ‘teoría cuántica’ no es teoría, sino hecho (mejor usar física cuántica), como tampoco lo es la ‘teoría de la evolución’ por mucho que lleven la palabra ‘teoría’ delante :-)))

  9. Anónimo, las estructuras no son clases, son… estructuras. Unos elementos que van alojados en la pila y que si se necesita que sean como clases, se copian al montículo.

    Si todo el asunto viene por eso: un elemento predefinido puede ser tanto un ente por valor como un objeto por referencia dependiendo de cómo trabajemos con él. La problemática reside en que si lo usamos como un ente y estaba en el montículo, ES COPIADO a la pila, y si luego lo volvemos a usar como objeto, ES COPIADO de nuevo al montículo. Y eso cuesta bastante tiempo de ejecución (y fragmentación de memoria, etc).

    Lo ideal IMHO hubiera sido evitar ese copiado y clarificar que un tipo básico es SIEMPRE un objeto y ya está.

    PS: No GRITO, sino que DESTACO el texto. 🙂

  10. Las diferencias entre clases y estructuras son en muchos casos obvias, y en muchos casos sutiles.

    Algunas diferencias:

    – Una variable que es de un tipo por valor NO puede ser null. Una variable que es de un tipo por referencia puede serlo. En realidad, desde .NET 2.0, sí que se pueden tener tipos por valor «nullables». Si no recuerdo mal, lo que hacía .NET por debajo era una pequeña pirueta encapsulando el tipo que quieres utilizar (int, por ejemplo) en otra estructura que te permite saber si es nulo o no.

    – La diferencia que todo el mundo ha mencionado: La ubicación. Por valor = pila. Por referencia = montículo.

    – Cuando se pasa un parámetro a un método que sea un tipo por valor, se copia el tipo entero, creando una instancia nueva. Cuando se pasa un parámetro que sea un tipo por referencia, se pasa POR VALOR la referencia, creando un alias. He puesto esas mayúsculas porque he visto una confusión en muchos sitios en los que dicen que se pasa por referencia. Esto es un error. Se pasa por valor. Pero lo que se pasa es una referencia.

    – En una clase puedes tener expresiones que inicializen campos, como:

    public int i = 1;

    En una estructura no puedes.

    – Una clase puede tener un constructor sin parámetros explícito. Una estructura no puede.

    – Las clases deben ser instanciadas utilizando el operador new. Con las estructuras no es oblilgatorio (aunque está permitido).

    – Las clases soportan herencia, pero las estructuras no.

    – Dado que las estructuras no soportan herencia, los modificadores de acceso protected o protected internal no están permitidos.

    – No es obligatorio inicializar todos los campos de una clase en el constructor. Sí es oblligatorio en una estructura.

    – Una clase puede tener un destructor. Una estructura no.

    Otras cosas no tan evidentes que se desprenden de lo comentado más arriba (y que se comentan en el CLR via C#, de Jeffrey Richter):

    – La destrucción de un objeto que reside en el montículo sucede de forma no determinista, y depende de la recolección de basura. La destrucción de una variable que reside en la pila es determinista. Cuando salimos del ámbito de la variable local, es destruida.

    ¿Alguna diferencia más?

  11. Hola Rafael… te voy a regañar… jhajaajaja no te creas apenas estaba buscando como podia hacer una relacion de enum ¬¬ hahaha bueno espero que pronto ustedes los programadores pongan tipo de proyectito con el codigo fuente, estilo tutorial como el del framework .net, por que en ingles esta canijo… espero que lo hagan y pronto! hahaha Bye

Responder a aruiz Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *