Tema 6: Espacios de nombres
(C) 2001 José Antonio González Seco
Concepto de espacio de
nombres
Del mismo modo que los ficheros se organizan en directorios, los
tipos de datos se organizan en espacios 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.
Definición de
espacios de nombres
Para definir un espacio de nombres se utiliza la siguiente
sintaxis:
namespace <nombreEspacio>
{
<tipos>
}
Los tipos que se definan en <tipos> pasarán a
considerase pertenecientes al espacio de nombres llamado
<nombreEspacio>. Como veremos más adelante, aparte de
clases esto tipos pueden ser también interfaces,
estructuras, tipos enumerados y delegados. A continuación se
muestra un ejemplo en el que definimos una clase de nombre
ClaseEjemplo perteneciente a un espacio de nombres llamado
EspacioEjemplo:
namespace EspacioEjemplo
{
class ClaseEjemplo
{}
}
El verdadero nombre de una clase, al que se denomina nombre
completamente calificado, es el nombre que le demos al
declararla prefijado por la concatenación de todos los
espacios de nombres a los que pertenece ordenados del más
externo al más interno y seguido cada uno de ellos por un
punto (carácter .) Por ejemplo, el verdadero nombre
de la clase ClaseEjemplo antes definida es
EspacioEjemplo.ClaseEjemplo. Si no definimos una clase dentro de
una definición de espacio de nombres -como se ha hecho en
los ejemplos de temas previos- se considera que ésta
pertenece al denominado espacio de nombres global y su
nombre completamente calificado coincidirá con el nombre que
le demos al definirla..
Aparte de definiciones de tipo, también es posible incluir
como miembros de un espacio de nombres a otros espacios de nombres.
Es decir, como se muestra el siguiente ejemplo es posible anidar
espacios de nombres:
namespace EspacioEjemplo
{
namespace EspacioEjemplo2
{
class ClaseEjemplo
{}
}
}
Ahora ClaseEjemplo tendrá
EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo como nombre
completamente calificado. En realidad es posible compactar las
definiciones de espacios de nombres anidados usando esta sintaxis
de calificación completa para dar el nombre del espacio de
nombres a definir. Es decir, el último ejemplo es
equivalente a:
namespace EspacioEjemplo.EspacioEjemplo2
{
class ClaseEjemplo
{}
}
En ambos casos lo que se ha definido es una clase llamada
ClaseEjemplo perteneciente al espacio de nombres llamado
EspacioEjemplo2 que, a su vez, pertenece al espacio de nombres
llamado EspacioEjemplo.
Importación de
espacios de nombres
Sentencia using
En principio, si desde código perteneciente a una clase
definida en un cierto espacio de nombres se desea hacer referencia
a tipos definidos en otros espacios de nombres, se ha de referir a
los mismos usando su nombre completamente calificado. Por ejemplo:
namespace EspacioEjemplo.EspacioEjemplo2
{
class ClaseEjemplo
{}
}
class Principal // Pertenece al espacio de nombres global
{
public static void Main ()
{
EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo c = new
EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo();
}
}
Como puede resultar muy pesado tener que escribir nombres tan
largos en cada referencia a tipos así definidos, en C# se ha
incluido un mecanismo de importación de espacios de nombres
que usa la siguiente sintaxis:
using <espacioNombres>;
Este tipo de sentencias siempre ha de aparecer dentro de una
definición de espacio de nombres antes que cualquier
definición de miembros de la misma y permiten indicar
cuáles serán los espacios de nombres que se
usarán implícitamente dentro de ese espacio de
nombres. A los miembros de los espacios de nombres así
importados se les podrá hacer referencia sin tener que usar
calificación completa, como muestra la siguiente
versión del último ejemplo:
using EspacioEjemplo.EspacioEjemplo2;
namespace EspacioEjemplo.EspacioEjemplo2
{
class ClaseEjemplo
{}
}
// (1)
class Principal // Pertenece al espacio de nombres global
{
public static void ()
{
// EspacioEjemplo.EspacioEjemplo2. está implícito
ClaseEjemplo c = new ClaseEjemplo();
}
}
Nótese que la sentencia using no podría
haberse incluido en la zona marcada en el código como (1) el
código porque entonces se violaría la regla de que
todo using ha aparecer en un espacio de nombres antes que
cualquier definición de miembro, ya que la definición
del espacio de nombres EspacioEjemplo.EspacioEjemplo2 es un miembro
del espacio de nombres global. Sin embargo, el siguiente
código si que sería válido:
namespace EspacioEjemplo.EspacioEjemplo2
{
class ClaseEjemplo
{}
}
namespace Principal
{
using EspacioEjemplo.EspacioEjemplo2;
class Principal // Pertenece al espacio de nombres global
{
public static void Main()
{
ClaseEjemplo c = new ClaseEjemplo();
}
}
}
En este caso el using aparece antes que cualquier otra
definición de tipos dentro del espacio de nombres en que se
incluye (Principal) Sin embargo, ahora la importación hecha
con el using sólo será válida dentro de
código incluido en ese mismo espacio de nombres, mientras
que en el caso anterior era válida en todo el fichero al
estar incluida en el espacio de nombres global.
Si una sentencia using importa miembros de igual nombre que
miembros definidos en el espacio de nombres donde se incluye, el
using no se produce error alguno pero se da preferencia a
los miembros no importados. Un ejemplo:
namespace N1.N2
{
class A {}
class B {}
}
namespace N3
{
using N1.N2;
class A {}
class C: A {}
}
En este ejemplo C deriva de N3.A en vez de N1.N2.A. Si queremos que
ocurra lo contrario tendremos que referenciar a N1.N2.A por su
nombre completo al definir C o, como se explica a
continuación, usar un alias.
Especificación de
alias
Aún en el caso de que usemos espacios de nombres distintos
para diferenciar clases con igual nombre pero procedentes de
distintos fabricantes, podrían darse conflictos sin usamos
sentencias using para importar los espacios de nombres de
dichos fabricantes ya que entonces al hacerse referencia a una de
las clases comunes con tan solo su nombre simple el compilador no
podrá determinar a cual de ellas en concreto nos referimos.
Por ejemplo, si tenemos una clase de nombre completamente
calificado A.Clase, otra de nombre B.Clase, y hacemos:
using A;
using B;
class EjemploConflicto: Clase {}
¿Cómo sabrá el compilador si lo que queremos
es derivar de A.Clase o de B.Clase? En realidad el compilador no
puede determinarlo y producirá un error informando de que
hay una referencia ambigua a Clase.
Para resolver ambigüedades de este tipo podría hacerse
referencia a los tipos en conflicto usando siempre sus nombres
completamente calificados, pero ello puede llegar a ser muy
fatigoso sobre todo si sus nombres son muy largos. Para solucionar
los conflictos de nombres sin tener que escribir tanto se ha
incluido en C# la posibilidad de definir alias para
cualquier tipo de dato, que son sinónimos para los mismos
que se definen usando la siguiente sintaxis:
using <alias> = <nombreCompletoTipo>;
Como cualquier otro using, las definiciones de alias
sólo pueden incluirse al principio de las definiciones de
espacios de nombres y sólo tienen validez dentro de las
mismas.
Definiendo alias distintos para los tipos en conflictos se
resuelven los problemas de ambigüedades. Por ejemplo, el
problema del ejemplo anterior se podría resolver así:
using A;
using B;
using ClaseA = A.Clase;
class EjemploConflicto: ClaseA {} // Heredamos de A.Clase
Los alias no tienen porqué ser sólo referentes a
tipos, sino que también es posible escribir alias de
espacios de nombres como muestra el siguiente ejemplo:
namespace N1.N2
{
class A {}
}
namespace N3
{
using R1 = N1;
using R2 = N1.N2;
class B
{
N1.N2.A a; // Campo de nombre completamente calificado N1.N2.A
R1.N2.A b; // Campo de nombre completamente calificado N1.N2.A
R2.A c; // Campo de nombre completamente calificado N1.N2.A
}
}
Al definir alias hay que tener cuidado con no definir en un mismo
espacio de nombres varios con igual nombre o cuyos nombres
coincidan con los de miembros de dicho espacio de nombres.
También hay que tener en cuenta que no se pueden definir
unos alias en función de otro, por lo que códigos
como el siguiente son incorrectos:
namespace N1.N2 {}
namespace N3
{
using R1 = N1;
using R2 = N1.N2;
using R3 = R1.N2; // ERROR: No se puede definir R3 en función de R1
}
Espacio de nombres
distribuidos
Si hacemos varias definiciones de un espacio de nombres en un mismo
fichero o en diferentes y se compilan todas juntas, el compilador
las fusionará en una sola definición cuyos miembros
serán la concatenación de los miembros definidos en
cada una de las definiciones realizadas. Por ejemplo:
namespace A // (1)
{
class B1 {}
}
namespace A // (2)
{
class B2 {}
}
Hacer una definición como la anterior es tratada por el
compilador exactamente igual que si se hubiese hecho:
namespace A
{
class B1 {}
class B2 {}
}
Lo mismo ocurriría si las definiciones marcadas como (1) y
(2) se hubiesen hecho en ficheros separados que se compilasen
conjuntamente.
Hay que tener en cuenta que las sentencias using, ya
sean de importación de espacios de nombres o de
definición de alias, no son consideradas miembros de los
espacios de nombres y por tanto no participan en sus fusiones.
Así, el siguiente código es inválido:
namespace A
{
class ClaseA {}
}
namespace B
{
using A;
}
namespace B
{
// using A;
class Principal: ClaseA {}
}
Este código no es válido debido a que aunque se
importa el espacio de nombres A al principio de una
definición del espacio de nombres donde se ha definido
Principal, no se importa en la definición en donde se deriva
Principal de A.ClaseA. Para que todo funcionase a la
perfección habría que descomentar la línea
comentada en el ejemplo.