sábado, 11 de julio de 2009

C sharp NET / Capítulo 5

C sharp NET / Capítulo 5

Introducción a las clases en C#
Como hemos dicho, C# es un lenguaje orientado a objetos. A diferencia de lenguajes como C++ o Python en los que la orientación a objetos es opcional, en C# y al igual que en Java, la orientación a objetos es ineludible, de hecho cualquier método o variable está contenida dentro de un objeto. Y el concepto fundamental en torno a la orientación a objetos es la clase.

Una clase es como una plantilla que describe cómo deben ser las instancias de dicha clase,
de forma que cuando creamos una instancia, ésta tendrá exactamente los mismos métodos
y variables que los que tiene la clase. Los datos y métodos contenidos en una clase se
llaman miembros de la clase y se accede a ellos siempre mediante el operador "." . En el
siguiente ejemplo, se definirá una clase, Clase1 y en el método Main se creará una
instancia de Clase1 llamada MiClase. Una buena idea es jugar un poco con el código para
ver que la instancia de la clase efectivamente tiene los mismos miembros que la clase
Clase1 (que sería la plantilla de la que hablábamos antes)
using System;
//definimos nuestra clase
class Clase1{
public int a = 1;
private double b = 3;
public char c = 'a';
}
//usamos la clase que hemos creado
class UsoClase{
public static void Main()
{
Clase1 MiClase = new Clase1(); // asi creamos una
instancia de Clase1
Console.WriteLine( MiClase.c ); //podemos llamar a los
tipos que hay dentro de Clase1
}
}
los identificadores public delante de los tipos que hay dentro de Clase1 son necesarios para
luego poder ser llamados desde otra clase, como en este caso, que estamos llamando a los
miembros de una instancia de Clase1 desde UsoClase. Pero en las clases no solo hay
variables, también podemos incluir métodos.
using System;
//definimos nuestra clase
class Clase1{
C sharp NET / Capítulo 5 2
public int a = 1;
public double b = 3;
public char c = 'a';
public void Descripcion()
{
Console.WriteLine("Hola, soy una clase");
}
}
//usamos la clase que hemos creado
class UsoClase{
public static void Main()
{
Clase1 MiClase = new Clase1(); // asi creamos una
instancia de Clase1
Console.WriteLine( MiClase.c ); //podemos usar todos los
tipos que hay dentro de Clase1
MiClase.Descripcion();
}
}
Podemos hacer más cosas con las clases, como heredar otras clases o implementar
interfaces, pero en este capítulo nos centraremos en el uso de métodos y variables.
Métodos
Los métodos, también llamados funciones, son trozos de código que reciben unos datos,
hacen algo con esos datos, y a veces devuelven algún valor. En C#, todos los métodos se
encuentran contenidas dentro de un objeto.
La estructura mínima de un método tiene las siguientes partes:
* Tipo devuelto
* Nombre del método
* Parámetros (puede ser vacío)
* Cuerpo del método
de forma que el siguiente método:
double Divide( double a, double b )
{
return a/b;
}
devuelve un tipo double, tiene por nombre Divide, los parámetos son a y b, ambos del tipo
double, y el cuerpo del método es simplemente "return a/b;".
Cuando queramos llamar a un método, debemos simplemente poner el nombre del método
y sus argumentos dentro de un paréntesis separados por comas. Para llamar al método
Divide declarado antes, simplemente debemos escribir Divide(8, 2);
C sharp NET / Capítulo 5 3
Según lo que hemos visto, el ejemplo del método Divide() completo necesita tener una clase
donde definirse y un método Main() donde ejecutarse.
using System;
class Metodo{
public double Divide( double a, double b )
{
return a/b;
}
}
class Principal{
public static void Main()
{
Metodo m = new Metodo();
Console.WriteLine( m.Divide(8, 2) );
}
}
Pasando valores a los métodos
Parámetros
La declaración formal de parámetros también define variables. Hay cuatro tipos de
parámetros: parámetros por valor, por referencia, parámetros de salida, y arreglos de
parámetros.
Paso por valor
El paso de parámetros por valor es usado por defecto para pasar parámetros a métodos.
Cuando se pasa un parámetro por valor a una función realmente se está pasando una copia
de dicho parámetro, por lo que las modificaciones que le hagamos al parámetro dentro del
método no afectarán al parámetro original. El ejemplo
using System;
class Test {
static void F(int p) {
p++;
Console.WriteLine("p = {0}", p);
}
static void Main() {
int a = 1;
Console.WriteLine("pre: a = {0}", a);
F(a);
Console.WriteLine("post: a = {0}", a);
}
}
C sharp NET / Capítulo 5 4
muestra un método F que tiene un parámetro por valor llamado p. El ejemplo produce la
salida:
pre: a = 1
p = 2
post: a = 1
aunque el valor del parámetro p haya sido modificado dentro del método, éste parámetro
solamente tenía una copia del valor del parámetro a que pasamos al método; por lo que
cuando imprimimos el parámetro a vemos que éste parámetro ha mantenido su valor
original.
Paso por referencia
El paso de parámetros por referencia es la contraposición lógica al paso por valor. En el
paso por referencia no se realiza ninguna copia del objeto, sino que lo que se le pasa a la
función es una referencia del objeto, de forma que el parámetro pasa directamente a la
función y cualquier modificación sobre el parámetro dentro de la función afectará al
parámetro original
using System;
class Test {
static void Swap(ref int a, ref int b) {
// intercambia los dos valores
int t = a;
a = b;
b = t;
}
static void Main() {
int x = 1;
int y = 2;
Console.WriteLine("pre: x = {0}, y = {1}", x, y);
Swap(ref x, ref y);
Console.WriteLine("post: x = {0}, y = {1}", x, y);
}
}
muestra un método swap que tiene dos parámetros por referencia. La salida producida es:
pre: x = 1, y = 2
post: x = 2, y = 1
La palabra clave ref debe de ser usada tanto en la declaración formal de la función como en
los usos que se hace de ésta.
C sharp NET / Capítulo 5 5
Parámetro de salida
El parámetro de salida es similar al parámetro por referencia, salvo que el valor inicial de
dicho argumento carece de importancia. Un argumento de salida se declara con el
modificador out. El ejemplo
using System;
class Test {
static void Divide(int num1, int num2, out int result, out int
resid) {
result = num1 / num2;
resid = num1 % num2;
}
static void Main() {
int valor1 = 10;
int valor2 = 3;
int respuesta, residuo;
Divide(valor1, valor2, out respuesta, out residuo);
Console.WriteLine("La división de {0} para {1} = {2} con un
residuo de {3}", valor1, valor2, respuesta, residuo);
}
}
muestra un método Divide que incluye dos parámetros de salida. Uno para el resultado
(variable result) de la división y otro para el resto (variable resid). Vemos que estos
resultados son asignados a las variables respuesta y residuo respectivamente.
Arreglo de parámetros
Habrá ocasiones que necesitemos pasar varios parámetros a un método (o función) pero no
sabremos con anticipación cuantos parámetros tendremos que pasar; para esto podremos
usar un arreglo de parámetros. Un arreglo de parámetros permite guardar una relación de
varios a uno: varios argumentos pueden ser representados por un único arreglo de
parámetros. En otras palabras, los arreglos de parámetros permiten listas de argumentos
de tamaño variable.
Un arreglo de parámetros se declara con el modificador params. Sólo puede haber un
arreglo de parámetros en cada método, y siempre debe ser el último parámetro
especificado. El tipo del arreglo de parámetros debe ser siempre un tipo arreglo
unidimensional. Al llamar a la función se puede pasar uno o varios argumentos del tipo
del arreglo. El ejemplo
using System;
class Test
{
static void F(params int[] args) {
Console.WriteLine("nº de argumentos: {0}", args.Length);
for (int i = 0; i < args.Length; i++)
Console.WriteLine("args[{0}] = {1}", i, args[i]);
}
C sharp NET / Capítulo 5 6
static void Main() {
F();
F(1);
F(1, 2);
F(1, 2, 3);
F(new int[] {1, 2, 3, 4});
}
}
muestra un método F que toma un número variable de argumentos int, y varias llamadas a
este método. La salida es:
nº de argumentos: 0
nº de argumentos: 1
args[0] = 1
nº de argumentos: 2
args[0] = 1
args[1] = 2
nº de argumentos: 3
args[0] = 1
args[1] = 2
args[2] = 3
nº de argumentos: 4
args[0] = 1
args[1] = 2
args[2] = 3
args[3] = 4
La mayoría de los ejemplos presentes en este capítulo utilizan el método WriteLine de la
clase Console. El comportamiento para las sustituciones, como muestra el ejemplo
int a = 1, b = 2;
Console.WriteLine("a = {0}, b = {1}", a, b);
se consigue usando un arreglo de parámetros. El método WriteLine proporciona varios
métodos sobrecargados para el caso común en el que se pasa un pequeño número de
argumentos, y un método que usa un arreglo de parámetros.
using System;
namespace System
{
public class Console
{
public static void WriteLine(string s) {...}
public static void WriteLine(string s, object a) {...}
public static void WriteLine(string s, object a, object b) {...}
...
public static void WriteLine(string s, params object[] args)
C sharp NET / Capítulo 5 7
{...}
}
}
Modificadores public y static
El modificador public lo hemos utilizado anteriormente. Se puede utilizar en la declaración
de cualquier método o variable, y como es de esperar, produce el efecto de que el campo
afectado se vuelve público, esto es, se puede utilizar desde otras clases
using System;
class Metodo{
public double Divide( double a, double b )
{
return a/b;
}
}
class Principal{
public static void Main()
{
Metodo m = new Metodo();
Console.WriteLine( m.Divide(8, 2) );
}
}
Si por ejemplo intentamos declarar el método Divide sin el modificador public, obtendremos
un error en tiempo de compilación. El modificador complementario de public es private,
que provoca que el método o dato solo sea accesible desde la clase en la que está
declarado. Si no se especifica nada, se toma por defecto el modificador private
De esta forma podríamos separar las clases Metodo y Principal en dos archivos separados,
llamados por ejemplo metodo.cs y principal.cs . Para compilar esto, bastará compilar ambos
archivos al mismo tiempo, de forma similar a esto: mcs principal.cs metodo.cs
Además, tampoco es necesario crear una instancia de la clase sólo para acceder a un
método declarado en ella. Para eso debemos anteponer a la declaración del método el
modificador static. Los métodos estáticos se caracterizan por no necesitar una instancia de
la clase para cumplir su función, pero como contrapartida, no pueden acceder a datos
propios de la clase.
using System;
class Metodo{
public static double Divide( double a, double b )
{
return a/b;
}
}
C sharp NET / Capítulo 5 8
class Principal{
public static void Main()
{
Console.WriteLine( Metodo.Divide(8, 2) );
}
}
Los métodos estáticos se utilizan en multitud de situaciones. Por ejemplo, el método
Console.WriteLine() o las funciones de la librería matemática estándar no son más que
métodos estáticos de sus respectivas clases.
Constructores e instancias de una clase
Como hemos visto, las instancias de una clase se crean con la sintaxis
nombreclase objeto = new nombreclase( argumentos );
donde nombreclase es el nombre que le hemos dado a la definición de la clase, argumentos
es una lista de argumentos posiblemente vacía y objeto es el nombre que queremos darle a
la instancia de la clase.
Una vez creada una clase, sus miembros se inicializan a sus valores predeterminados ( cero
para valores numéricos, cadena vacía para el tipo string, etc. ). La siguiente clase
representa un punto sobre el plano, de forma que tiene dos valores públicos X e Y, y un
método que calcula la distancia al origen del punto (módulo)
using System;
class Punto{
public double X;
public double Y;
public double Modulo()
{
double d;
d = Math.Sqrt(X*X + Y*Y); //Sqrt = raiz cuadrada
return d;
}
}
class Principal{
public static void Main()
{
Punto A = new Punto();
A.X = 1;
A.Y = 1;
Console.WriteLine("El modulo del punto (1,1) es: {0}",
A.Modulo() );
C sharp NET / Capítulo 5 9
}
}
Ahora bien, la forma en la que se crea la instancia, es decir, inicializando los datos a cero
(ejercicio: comprobar esto), se puede personalizar, de forma que podemos construir nuestro
propio constructor que le diga a la clase los valores por defecto que debe tomar. Esto se
realiza simplemente escribiendo dentro de la clase un método que tenga el mismo nombre
que la clase y en el que no se especifica el valor devuelto. La clase Punto con un
constructor sería así:
using System;
class Punto{
public double X;
public double Y;
public Punto() //constructor
{
X = 1;
Y = 1;
}
public double Modulo()
{
double d;
d = Math.Sqrt(X*X + Y*Y); //Sqrt = raiz cuadrada
return d;
}
}
de forma que ahora al crear una instancia de la clase se crea el punto (1,1) en lugar del
(0,0), que era el que se creaba por defecto. De esta forma, al crear la instancia, par ya
contendrá los valores (1,1) .
En la práctica se utilizan mucho constructores con parámetos, de forma que al crear la
instancia se le asignan valores según los parámetros. La siguiente implementación de Par
contiene un constructor que acepta un par de valores, que servirán para inicializar los
valores A y B
class Punto{
public Punto( double val1, double val2)
{
X = val1;
Y = val2;
}
...
}
C sharp NET / Capítulo 5 10
También tenemos la posibilidad de declarar una clase con varios constructores (cada uno
con diferentes parámetros) Lo que hará el compilador de C# es buscar el constructor que
se adecúe a los parámetros que le llegan, y ejecutarlo como si fuera un método más.
Dependiendo de la llamada que se haga en el "new", usaremos un constructor u otro.
Sobrecarga de métodos
En C#, al igual que en C++ y en Java es posible definir varios métodos con el mismo
nombre pero con distintos parámetros, de forma que el compilador decide a cuál se llama
dependiendo de los parámetros que le lleguen.
Esto es muy práctico, pues no tienes que renombrar cada función según el tipo de valor que
acepta. El siguiente ejemplo implementa un par de métodos que elevan al cuadrado el valor
que reciben, y se implementan para tipos double y para int. En C, que es un lenguaje que
no soporta sobrecarga de métodos, se tendría que haber llamado distinto a ambos métodos,
por ejemplo alcuadrado_double y alcuadrado_int
using System;
class Eleva{
public static double AlCuadrado( int a )
{
return a*a;
}
public static double AlCuadrado( double a )
{
return a*a;
}
}
class Principal{
public static void Main()
{
Console.WriteLine("4 al cuadrado es {0}",
Eleva.AlCuadrado(4) );
Console.WriteLine("3.2 al cuadrado es {0}",
Eleva.AlCuadrado(3.2) );
}
}
C sharp NET / Capítulo 5 11
La palabra reservada this
La palabra reservada this sirve para hacer referencia a miembros de la clase en caso de
que se quiera especificar, ya sea por motivos de colisión de nombres o por la claridad del
código. Su sintaxis es
this.campo
donde campo es la variable de la clase a la que queremos hacer referencia.
En el siguiente ejemplo, declaramos un constructor para la clase Punto, que toma dos
argumentos X e Y. Entonces es obligado el uso de this para distinguir entre el X de la clase
y el X tomado como parámetro
class Complejo
{
double X;
double Y;
Complejo(double X, double Y)
{
this.X = X;
this.Y = Y;
}
}
Propiedades e indizadores
Propiedades
Las propiedades son una característica de C# que permiten aparentemente el acceso a un
miembro de la clase mientras mantiene el control asociado al acceso mediante métodos.
Para los programadores de Java hay que decir que esto no es más que la formalización del
patrón de asignación (setter) y método de lectura (getter)
Las propiedades son como métodos que se declaran dentro de un bloque asociado a una
variable mediante las palabras reservadas get (se encarga de devolver algo cuando se llama
al tipo que lo contiene ) y set (que hace algo cuando se le asigna un valor a la variable que
lo contiene. Este valor viene especificado en la variable value )
using System;
class TestProperties {
private static string clave;
public string Clave {
get
{
Console.WriteLine ("Acceso a la propiedad
clave");
return clave;
}
set
C sharp NET / Capítulo 5 12
{
Console.WriteLine ("Cambio del valor de
clave");
clave = value;
}
}
}
class Test {
public static void Main () {
TestProperties tp = new TestProperties();
string c = "ClaveClave";
tp.Clave = c;
Console.WriteLine (tp.Clave);
}
}
En realidad, lo que se hace es declarar una variable privada de forma que no se puede
acceder de forma directa, y se crean dos métodos ( o uno si solo se requiere acceso de
lectura) que permiten acceder al contenido de la variable y tal vez modificarla. Si no
queremos que se pueda modificar la variable, no incluímos el método "set" y ya tendríamos
propiedades de sólo lectura.
Indexadores
Hemos visto, en el apartado en el que tratamos las propiedades, que podemos acceder a
una variable privada de una clase a través de eventos que nos permiten controlar la forma
en la que accedemos a dicha variable.
Los indexadores nos van a permitir hacer algo parecido. Nos van a permitir acceder a una
clase como si se tratara de un arreglo. Lo vemos de forma más sencilla con un ejemplo:
using System;
class PruebaIndexadores
{
private int[] tabla = {1, 2, 3, 4};
public int this [int indice]
{
get
{
Console.WriteLine ("La posicion {0} de la tabla tiene el valor
{1}", indice, tabla[indice]);
return tabla[indice];
}
set
{
Console.WriteLine ("Escrito el valor {0} en la posición {1} de
la tabla", value, indice);
C sharp NET / Capítulo 5 13
tabla[indice] = value;
}
}
}
Tenemos una clase PruebaIndexadores en la que hay un array llamado "tabla", declarado
como privado, por lo que no podremos acceder a él desde fuera de nuestra clase. Pero
hemos declarado también un indexador (public int this [int indice]), que nos permitirá
acceder a él de forma más controlada.
Para probar esta clase, creamos otra clase con un punto de entrada (public static void Main
()), que será donde hagamos las pruebas.
Primero creamos un objeto de la clase PruebaIndexadores:
PruebaIndexadores obj = new PruebaIndexadores ();
Luego accedemos a una posición del indexador:
int a = obj[3];
Esta línea lo que hace es llamar al indexador, pasándole como parámetro el índice, en este
caso 3. Al ser una consulta de lectura, se ejecuta el código que haya en la parte "get" del
indexador. Una vez ejecutado, lo que nos aparece por pantalla es esto:
La posicion 3 de la tabla tiene el valor 4
Vamos ahora a hacer un cambio en la tabla:
obj[3] = 6;
Lo que se ejecuta ahora es la parte "set" del indexador. Lo que aparecerá en pantalla una
vez ejecutado esto será:
Escrito el valor 6 en la posición 3 de la tabla
Nótese que tenemos que hacer explícitamente el acceso al array (tabla[indice]=value) en el
set, ya que el indexador no tiene forma de saber qué variable se supone que tiene que
manejar. Si no pusiéramos esa línea, en realidad el indexador no cambiaría el valor del
array.
Para comprobar que realmente se ha hecho el cambio, volvemos a acceder al indexador:
a = obj[3];
Y esta vez nos aparecerá esto:
La posicion 3 de la tabla tiene el valor 6.

1 comentario:

  1. El intento es muy bueno. Pero me parece que la descripciones teóricas son muy densas, y eso complica las cosas. Por ejemplo, los trozos de código deberías formatearlos para hacerlos más entendibles. Deberías basarte un poco en webs que ya llevan un cierto recorrido en este cuento de los cursos online como www.elguille.info o www.pedrov.phpnet.us

    ResponderEliminar