Zona HTML Zona Java Zona PHP Zona ASP Zona Bases de datos
Inicio > Tutoriales > Lenguajes orientados a objeto > C# > El lenguaje de programación C#
-Tutoriales

El lenguaje de programación C#


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.

 
Patrocinados
 

Copyright © 1999-2006 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network