• No se han encontrado resultados

En realidad, dentro la definición de un tipo de dato no tienen porqué incluirse sólo definiciones de miembros comunes a todos sus objetos, sino también pueden definirse miembros ligados al tipo como tal y no a los objetos del mismo. Para ello basta preceder la definición de ese miembro de la palabra reservada static, como muestra este ejemplo:

class A {

int x; static int y; }

Los objetos de clase A sólo van a disponer del campo x, mientras que el campo y va a pertenecer a la clase A. Por esta razón se dice que los miembros con modificador static

son miembros de tipo y que los no lo tienen son miembros de objeto.

Para acceder a un miembro de clase ya no es válida la sintaxis hasta ahora vista de <objeto>.<miembro>, pues al no estar estos miembros ligados a ningún objeto no podría ponerse nada en el campo <objeto>. La sintaxis a usar para acceder a estos miembros será <nombreClase>.<miembro>, como muestra ejemplo donde se asigna el valor 2 al miembro yde la clase Adefinida más arriba:

A.y = 2;

Nótese que la inclusión de miembros de clase rompe con la afirmación indicada al principio del tema en la que se decía que C# es un lenguaje orientado a objetos puro en el que todo con lo que se trabaja son objetos, ya que a los miembros de tipo no se les accede a través de objetos sino nombres de tipos.

Es importante matizar que si definimos una función como static, entonces el código de

la misma sólo podrá acceder implícitamente (sin sintaxis <objeto>.<miembro>) a otros miembros static del tipo de dato al que pertenezca. O sea, no se podrá acceder a ni a los miembros de objeto del tipo en que esté definido ni se podrá usar this ya que el método

no está asociado a ningún objeto. O sea, este código sería inválido: int x;

static void Incrementa() {

x++; //ERROR: x es miembro de objeto e Incrementa() lo es de clase. }

También hay que señalar que los métodos estáticos no entran dentro del mecanismo de redefiniciones descrito en este mismo tema. Dicho mecanismo sólo es aplicable a métodos de objetos, que son de quienes puede declararse variables y por tanto puede actuar el polimorifsmo. Por ello, incluir los modificadores virtual,override oabstract al

definir un método static es considerado erróneo por el compilador. Eso no significan

que los miembros static no se hereden, sino tan sólo que no tiene sentido redefinirlos.

Ya hemos visto que la herencia y el polimorfismo eran dos de los pilares fundamentales en los que es apoya la programación orientada a objetos. Pues bien, el tercero y último es la encapsulación, que es un mecanismo que permite a los diseñadores de tipos de datos determinar qué miembros de los tipos creen pueden ser utilizados por otros programadores y cuáles no. Las principales ventajas que ello aporta son:

• Se facilita a los programadores que vaya a usar el tipo de dato (programadores clientes) el aprendizaje de cómo trabajar con él, pues se le pueden ocultar todos los detalles relativos a su implementación interna y sólo dejarle visibles aquellos que puedan usar con seguridad. Además, así se les evita que cometan errores por manipular inadecuadamente miembros que no deberían tocar.

• Se facilita al creador del tipo la posterior modificación del mismo, pues si los programadores clientes no pueden acceder a los miembros no visibles, sus aplicaciones no se verán afectadas si éstos cambian o se eliminan. Gracias a esto es posible crear inicialmente tipos de datos con un diseño sencillo aunque poco eficiente, y si posteriormente es necesario modificarlos para aumentar su eficiencia, ello puede hacerse sin afectar al código escrito en base a la no mejorada de tipo. La encapsulación se consigue añadiendo modificadores de acceso en las definiciones de miembros y tipos de datos. Estos modificadores son partículas que se les colocan delante para indicar desde qué códigos puede accederse a ellos, entendiéndose por acceder el hecho de usar su nombre para cualquier cosa que no sea definirlo, como llamarlo si es una función, leer o escribir su valor si es un campo, crear objetos o heredar de él si es una clase, etc.

Por defecto se considera que los miembros de un tipo de dato sólo son accesibles desde código situado dentro de la definición del mismo, aunque esto puede cambiarse precediendolos de uno los siguientes modificadores (aunque algunos de ellos ya se han explicado a lo largo del tema, aquí se recogen todos de manera detallada) al definirlos:

public: Puede ser accedido desde cualquier código.

protected: Desde una clase sólo puede accederse a miembros protected de objetos de esa misma clase o de subclases suyas. Así, en el siguiente código las instrucciones comentadas con // Error no son válidas por lo escrito junto a ellas:

public class A {

protected int x;

static void F(A a, B b, C c) { a.x = 1; // Ok b.x = 1; // Ok c.x = 1; // OK } } public class B: A {

static void F(A a, B b, C c) {

b.x = 1; // Ok c.x = 1; // Ok } } public class C: B {

static void F(A a, B b, C c) {

//a.x = 1; // Error, ha de accederse a traves de objetos tipo C //b.x = 1; // Error, ha de accederse a traves de objetos tipo C

c.x = 1; // Ok }

}

Obviamente siempre que se herede de una clase se tendrá total acceso en la clase hija –e implíctiamente sin necesidad de usar la sintaxis <objeto>.<miembro>- a los miembros que ésta herede de su clase padre, como muestra el siguiente ejemplo:

using System; class A { protected int x=5; } class B:A { B() {

Console.WriteLine(“Heredado x={0} de clase A”, x); }

public static void Main() {

new B(); }

}

Como es de esperar, la salida por pantalla del programa de ejemplo será: Heredado x=5 de clase A

A lo que no se podrá acceder desde una clase hija es a los miembros protegidos de otros objetos de su clase padre, sino sólo a los heredados. Es decir:

using System; class A { protected int x=5; } class B:A { B(A objeto) {

Console.WriteLine(“Heredado x={0} de clase A”, x); Console.WriteLine(objeto.x); // Error, no es el x heredado

}

public static void Main() {

new B(new A()); }

}

private: Sólo puede ser accedido desde el código de la clase a la que pertenece.

Es lo considerado por defecto.

internal: Sólo puede ser accedido desde código perteneciente al ensamblado

en que se ha definido.

protected internal: Sólo puede ser accedido desde código perteneciente al

ensamblado en que se ha definido o desde clases que deriven de la clase donde se ha definido.

Si se duda sobre el modificador de visibilidad a poner a un miembro, es mejor ponerle inicialmente el que proporcione menos permisos de accesos, ya que si luego detecta que necesita darle más permisos siempre podrá cambiárselo por otro menos restringido. Sin embargo, si se le da uno más permisivo de lo necesario y luego se necesita cambiar por otro menos permisivo, los códigos que escrito en base a la versión más permisiva que dependiesen de dicho miembro podrían dejar de funcionar por quedarse sin acceso a él. Es importante recordar que toda redefinición de un método virtual o abstracto ha de realizarse manteniendo los mismos modificadores que tuviese el método original. Es decir, no podemos redefinir un método protegido cambiando su accesibilidad por pública, pues si el creador de la clase base lo definió así por algo sería.

Respecto a los tipos de datos, por defecto se considera que son accesibles sólo desde el mismo ensamblado en que ha sido definidos, aunque también es posible modificar esta consideración anteponiendo uno de los siguientes modificadores a su definición:

public: Es posible acceder a la clase desde cualquier ensamblado.

internal: Sólo es posible acceder a la clase desde el ensamblado donde se

declaró. Es lo considerado por defecto.

También pueden definirse tipos dentro de otros (tipos internos) En ese caso serán considerados miembros del tipo contenedor dentro de la que se hayan definido, por lo que les serán aplicables todos los modificadores válidos para miembros y por defecto se considerará que, como con cualquier miembro, son privados. Para acceder a estos tipos desde código externo a su tipo contenedor (ya sea para heredar de ellos, crear objetos suyos o acceder a sus miembros estáticos), además de necesitarse los permisos de acceso necesarios según el modificador de accesibilidad al definirlos, hay que usar la notación <nombreTipoContendor>.<nombreTipoInterno>, como muestra en este ejemplo:

class A // No lleva modificador, luego se considera que es internal {

public class AInterna {} // Si ahora no se pusiese public se consideraría private }

class B:A.AInterna // B deriva de la clase interna AInterna definida dentro de A. Es {} // válido porque A.AInterna es pública

Nótese que dado que los tipos externos están definidos dentro de su tipo externo, desde ellos es posible acceder a los miembros estáticos privados de éste. Sin embargo, hay que señalar que no pueden acceder a los miembros no estáticos de su tipo contenedor.

TEMA 6: Espacios de nombres

Concepto de espacio de nombres

Del mismo modo que los ficheros se organizan en directorios, los tipos de datos se organizan en espacio de nombres.

Por un lado estos espacios permiten tener más organizados los tipos de datos, lo que facilita su localización. De hecho, esta es la forma en que se encuentra organizada la BCL, de modo que todas las clases más comúnmente usadas en cualquier aplicación pertenecen al espacio de nombres llamado System, las de acceso a bases de datos en

System.Data, las de realización de operaciones de entrada/salida en System.IO, etc

Por otro lado, los espacios de nombres también permiten poder usar en un mismo programa varias clases con igual nombre si pertenecen a espacios diferentes. La idea es que cada fabricante defina sus tipos dentro de un espacio de nombres propio para que así no hayan conflictos si varios fabricantes definen clases con el mismo nombre y se quieren usar a la vez en un mismo programa. Obviamente para que esto funcione no han de coincidir los nombres los espacios de cada fabricante, y una forma de conseguirlo es dándoles el nombre de la empresa fabricante, o su nombre de dominio en Internet, etc.