miércoles, 24 de febrero de 2010

Pasar parámetros Reference-Type

Una variable de un tipo de referencia no contiene directamente sus datos; contiene una referencia a ellos. Cuando se pasa un parámetro de tipo de referencia por valor, es posible cambiar los datos a los que apunta la referencia, como el valor de un miembro de clase. Sin embargo, no se puede cambiar el valor de la propia referencia; es decir, no se puede utilizar la misma referencia para asignar memoria a una nueva clase y hacer que persista fuera del bloque. Para hacer esto, se debe pasar el parámetro mediante la palabra clave ref u out. Para simplificar, los siguientes ejemplos utilizan ref.

Ejemplo 1: pasar tipos de referencia por valor
El siguiente ejemplo ilustra el paso de un parámetro de tipo de referencia, arr, por valor, a un método, Change. Como el parámetro es una referencia a arr, es posible cambiar los valores de los elementos de la matriz. Sin embargo, el intento de volver a asignar el parámetro a otra ubicación de memoria sólo funciona dentro del método y no afecta a la variable original, arr.

class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[0] = 888; // Esto afecta el elmento original.
pArray = new int[5] {-3, -1, -2, -3, -4}; // Este cambio es local.
System.Console.WriteLine("Dentro del metodo,
el primer elemento es: {0}", pArray[0]);
}

static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Dentro del Main, antes de llamar el metodo, el primer elemento es: {0}", arr [0]);

Change(arr);
System.Console.WriteLine("Dentro del Main, despues de llamar el metodo, el primer elmento es: {0}", arr [0]);
}
}
Resultados
Dentro del Main, antes de llamar el metodo, el primer elemento es: 1

Dentro del metodo, el primer elemento es: -3

Dentro del Main, despues de llamar el metodo, el primer elemento es: 888


Descripción del código
En el ejemplo anterior, la matriz arr, que es un tipo de referencia, se pasa al método sin el parámetro ref. En ese caso, lo que se pasa al método es una copia de la referencia, la cual apunta a arr. El resultado muestra cómo el método puede cambiar el contenido de un elemento de la matriz, en este caso de 1 a 888. Sin embargo, cuando se asigna una nueva porción de memoria mediante el operador new dentro del método Change, la variable pArray hace referencia a una nueva matriz. De este modo, cualquier cambio posterior no afectará a la matriz original, arr, que se crea dentro de Main. De hecho, en este ejemplo se crean dos matrices, una dentro de Main y otra dentro del método Change.

Ejemplo 1: pasar tipos de referencia por referencia
Este ejemplo es el mismo que el ejemplo anterior, excepto que utiliza la palabra clave ref en el encabezado del método y en la llamada. Cualquier cambio que se realice en el método afectará a las variables originales del programa que hace la llamada.

class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// Los dos siguientes cambios afectaran las variables originales:
pArray[0] = 888;
pArray = new int[5] {-3, -1, -2, -3, -4};
System.Console.WriteLine("Dentro del metodo, el primer elemento es: {0}", pArray[0]);
}

static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Dentro del Mail, antes de llamar el metodo, el primer elemento es: {0}", arr[0]);

Change(ref arr);
System.Console.WriteLine("Dentro del Main, desdpues de llamar el metodo, el primer elemento es: {0}", arr[0]);
}
}
Resultados
Dentro del Main, antes de llamar el metodo, el primer elemento es: 1

Dentro del metodo, el primer elemento es: -3

Dentro del Main, despues de llamar el metodo, el primer elemento es: -3

Descripción del código
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en Main. De hecho, la matriz original se reasigna mediante el operador new. De esta forma, después de llamar al método Change, cualquier referencia a arr apunta a la matriz de cinco elementos que se crea en el método Change.

Ejemplo: intercambiar dos cadenas
El intercambio de cadenas constituye un buen ejemplo de paso de parámetros de tipo de referencia por referencia. En el ejemplo, se inicializan dos cadenas, str1 y str2, en Main y se pasan al método SwapStrings como parámetros modificados por la palabra clave ref. Las dos cadenas se intercambian dentro del método y también dentro de Main.

class SwappingStrings
{
static void SwapStrings(ref string s1, ref string s2)
// El parametro string es pasado por referencia.
// Ningun cambio sobre los parametros afectaran las variables originales.

{
string temp = s1;
s1 = s2;
s2 = temp;
System.Console.WriteLine("Dentro del metodo: {0} {1}", s1, s2);
}

static void Main()
{
string str1 = "John";
string str2 = "Nunez";
System.Console.WriteLine("Dentro de Main, antes del intercambio: {0} {1}", str1, str2);

SwapStrings(ref str1, ref str2); //Pasando el string por referencia
System.Console.WriteLine("Dentro del Main, despues del intercambio: {0} {1}", str1, str2);
}
}
Resultados
Dentro del Main, antes del intercambio: John Nunez

Dentro del metodo: Nunez John

Dentro del Main, despues del intercambio: Nunez John


Descripción del código
En este ejemplo, los parámetros se deben pasar por referencia para que afecten a las variables del programa que realiza la llamada. Si se elimina la palabra clave ref del encabezado del método y de la llamada al método, no se producirá ningún cambio en el programa que realiza la llamada.

Pasar parámetros de tipo de valor

Una variable de tipo de valor contiene directamente los datos, a diferencia de una variable de tipo de referencia, que contiene una referencia a los datos. Por lo tanto, pasar una variable de tipo de valor a un método significa pasar una copia de la variable al método. Cualquier cambio en el parámetro que se produzca dentro del método no afectará a los datos originales almacenados en la variable. Si se desea que el método llamado pueda cambiar el valor del parámetro, deberá pasarlo por referencia, utilizando la palabra clave ref uout. Para simplificar, los siguientes ejemplos utilizan ref.

Ejemplo 1: pasar tipos de valor por valor
El siguiente ejemplo ilustra el paso de parámetros de tipo de valor por valor. La variable n se pasa por valor al método SquareIt. Cualquier cambio que se produzca dentro del método no afectará al valor original de la variable.

class PassingValByVal
{
static void SquareIt(int x)
// El parametro x es pasado por valor.
// Los cambios a x no afectaran el valor original de x.
{
x *= x;
System.Console.WriteLine("El valor dentro del metodo: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("El valor antes de llamar al metodo: {0}", n);

SquareIt(n); // Pasando la variable por valor.
System.Console.WriteLine("El valor despues de llamar el metodo: {0}", n);
}
}
Resultados
The value before calling the method: 5

The value inside the method: 25

The value after calling the method: 5

Descripción del código
La variable n, que es un tipo de valor, contiene su dato, el valor 5. Cuando se invoca SquareIt, el contenido de n se copia en el parámetro x, el cual se eleva al cuadrado dentro del método. Sin embargo, en Main, el valor de n es el mismo, antes y después de llamar al método SquareIt. De hecho, el cambio que se produce dentro del método sólo afecta a la variable local x.

Ejemplo 1: pasar tipos de valor por referencia
El siguiente ejemplo es igual que el ejemplo anterior, excepto que el parámetro se pasa mediante la palabra clave ref. El valor del parámetro cambia después de llamar al método.

class PassingValByRef
{
static void SquareIt(ref int x)
// El parametro x es pasado por referencia.
// Los cambios a x afectaran el valor original de x.

{
x *= x;
System.Console.WriteLine("El valor dentro del metodo: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("El valor antes de llamar el metodo: {0}", n);

SquareIt(ref n); // Pasando la variable por referencia.
System.Console.WriteLine("El valor despues de llamar el metodo: {0}", n);
}
}
Resultados
The value before calling the method: 5

The value inside the method: 25

The value after calling the method: 25

Descripción del código
En este ejemplo, no se pasa el valor de n, sino una referencia a n. El parámetro x no es de tipo int; es una referencia a un valor de tipo int, en este caso, una referencia a n. Por lo tanto, cuando x se eleva al cuadrado dentro del método, lo que realmente se eleva al cuadrado es la variable a la que x hace referencia, es decir, n.

Ejemplo 3: intercambiar tipos de valor
Un ejemplo habitual de cambio de valores de los parámetros pasados es el método Swap, en el que se pasan dos variables, x e y, y el método intercambia su contenido. Los parámetros se deben pasar al método Swap por referencia; de otro modo, se estarán utilizando copias locales de los parámetros dentro del método. El siguiente es un ejemplo del método Swap que utiliza parámetros por referencia:

static void SwapByRef(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
Cuando se invoca este método, se debe utilizar la palabra clave ref en la llamada, como se muestra a continuación:

static void Main()
{
int i = 2, j = 3;
System.Console.WriteLine("i = {0} j = {1}" , i, j);

SwapByRef (ref i, ref j);

System.Console.WriteLine("i = {0} j = {1}" , i, j);
}
Resultados
i = 2 j = 3

i = 3 j = 2

Métodos

Los métodos son un bloque de código que contiene una serie de instrucciones. En C#, cada instrucción se ejecuta en el contexto de un método.

Los métodos se declaran en una clase o estructura especificando el nivel de acceso, el valor devuelto, el nombre del método y los parámetros de método. Los parámetros de método se incluyen entre paréntesis y separados por comas. Los paréntesis vacíos indican que el método no requiere ningún parámetro. Esta clase contiene tres métodos:

class Motorcycle
{
public void StartEngine() { }
public void AddGas(int gallons) { }
public int Drive(int miles, int speed) { return 0; }
}
Llamar a un método en un objeto es similar a tener acceso a un campo. Después del nombre de objeto, agregue un punto, el nombre del método y paréntesis. Los argumentos se enumeran entre paréntesis y separados por comas. Por tanto, se puede llamar a los métodos de la clase Motorcycle del modo siguiente:

Motorcycle moto = new Motorcycle();

moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
Parámetros de métodos
Como se muestra en el fragmento de código anterior, para pasar argumentos a un método simplemente hay que proporcionarlos entre paréntesis cuando se llama al método. En el método al que se llama, los argumentos de entrada se denominan parámetros.

Los parámetros que un método recibe también se proporcionan entre paréntesis, pero se debe especificar el tipo y nombre de cada parámetro. El nombre no tiene por qué ser igual que el argumento. Por ejemplo:

public static void PassesInteger()
{
int fortyFour = 44;
TakesInteger(fortyFour);
}
static void TakesInteger(int i)
{
i = 33;
}
Aquí un método denominado PassesInteger pasa un argumento a un método denominado TakesInteger. En PassesInteger el argumento se denomina fortyFour, pero en TakeInteger es un parámetro denominado i. Este parámetro sólo existe dentro del método TakesInteger. Otras variables también pueden denominarse i y pueden ser de cualquier tipo, siempre y cuando no sean parámetros o variables declaradas en ese método.

Observe que TakesInteger asigna un nuevo valor al argumento proporcionado. Se podría esperar que este cambio se reflejara en el método PassesInteger una vez que TakeInteger devuelve un valor, pero de hecho, el valor de la variable fortyFour se mantiene sin cambios. Esto se debe a que int es un tipo de valor. De forma predeterminada, cuando un tipo de valor se pasa a un método, se pasa una copia en lugar del propio objeto. Dado que son copias, cualquier cambio realizado en el parámetro no tiene ningún efecto en el método de llamada. Los tipos de valor reciben su nombre del hecho de que se pasa una copia del objeto en lugar del propio objeto. Se pasa el valor, pero no el objeto mismo.

Esto es diferente de los tipos de referencia, que se pasan por referencia. Cuando un objeto basado en un tipo de referencia se pasa a un método, no se realiza ninguna copia del objeto. En su lugar, se hace una referencia al objeto que se utiliza como argumento del método y se pasa dicha referencia. Los cambios realizados a través de esta referencia se reflejarán por consiguiente en el método de llamada. Un tipo de referencia se crea con la palabra clave class, de la forma siguiente:

public class SampleRefType
{
public int value;
}
Ahora, si un objeto basado en este tipo se pasa a un método, se pasará por referencia. Por ejemplo:

public static void TestRefType()
{
SampleRefType rt = new SampleRefType();
rt.value = 44;
ModifyObject(rt);
System.Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
obj.value = 33;
}
Este ejemplo hace esencialmente lo mismo que el ejemplo anterior. Pero, como se utiliza un tipo de referencia, la modificación realizada por ModifyObject se efectúa en el objeto creado en el método TestRefType. Por consiguiente, el método TestRefType mostrará el valor 33.

Valores devueltos
Los métodos pueden devolver un valor al llamador. Si el tipo de valor devuelto (el que aparece antes del nombre de método) no es void, el método puede devolver el valor mediante la palabra clave return. Una instrucción con la palabra clave return, seguida de un valor que coincida con el tipo de valor devuelto, devolverá ese valor al llamador del método. La palabra clave return también detiene la ejecución del método. Si el tipo de valor devuelto es void, una instrucción return sin ningún valor sigue siendo útil para detener la ejecución del método. Sin la palabra clave return, el método detendrá la ejecución cuando llegue al fin del bloque de código. Es necesario que los métodos con un tipo de valor devuelto no nulo utilicen la palabra clave return para devolver un valor. Por ejemplo, estos dos métodos utilizan la palabra clave return para devolver enteros:

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}

public int SquareANumber(int number)
{
return number * number;
}
}
Para emplear un valor devuelto por un método, el método de llamada puede utilizar la propia llamada del método en cualquier parte donde un valor del mismo tipo sea suficiente. El valor devuelto también se puede asignar a una variable. Por ejemplo, los dos ejemplos de código siguientes logran el mismo objetivo:

int result = obj.AddTwoNumbers(1, 2);
obj.SquareANumber(result);
obj.SquareANumber(obj.AddTwoNumbers(1, 2));
El uso de una variable intermedia, en este caso result, para almacenar un valor es opcional. La legibilidad del código puede ser útil o puede ser necesaria si el valor se va a utilizar más de una vez.

Miembros

Las clases y estructuras tienen miembros que representan sus datos y comportamiento. Esos miembros incluyen:

Campos
Los campos son instancias de objetos que se consideran parte de una clase y que normalmente contienen datos de la clase. Por ejemplo, una clase de calendario puede tener un campo con la fecha actual.

Propiedades
Las propiedades son métodos de una clase a los que se obtiene acceso como si fueran campos de esa clase. Una propiedad puede proporcionar protección a un campo de clase con el fin de evitar que se cambie sin el conocimiento del objeto.

Métodos
Los métodos definen las acciones que una clase puede realizar. El método puede aceptar parámetros que proporcionan datos de entrada y puede devolver datos de salida a través de parámetros. Los métodos también pueden devolver un valor directamente, sin utilizar ningún parámetro.

Eventos
Los eventos son una manera de proporcionar a otros objetos notificaciones sobre lo que ocurre, como clics en botones o la realización correcta de un método. Los eventos se definen y desencadenan mediante delegados. Para obtener más información, vea Eventos y delegados.

Operadores
Los operadores son condiciones o símbolos que realizan operaciones en operandos, por ejemplo +, *, <, etc. Los operadores se pueden volver a definir para realizar operaciones en tipos de datos personalizados. Para obtener más información, vea Operadores sobrecargables.

Indizadores
Los indizadores permiten indizar un objeto de una manera similar a como se hace con las matrices.

Constructores
Los constructores son métodos a los que se llama cuando el objeto se crea por primera vez. Se utilizan a menudo para inicializar los datos del objeto.

Destructores
Los destructores son métodos a los que llama el motor de ejecución cuando el objeto está a punto de quitarse de la memoria. Generalmente se utilizan para asegurarse de que los recursos que se tienen que liberar se controlan apropiadamente.

Tipos anidados
Los tipos anidados son tipos declarados dentro de una clase o estructura. Los tipos anidados se utilizan a menudo para describir objetos únicamente utilizados por los tipos que los contienen.

Cómo: Implementar explícitamente miembros de interfaz

En este ejemplo se declara una interfaz IDimensiones y una clase Caja, la cual implementa explícitamente los miembros de la interfaz getLargo y getAncho. El acceso a los miembros se realiza a través de la instancia de interfaz dimensiones.

Ejemplo:
interface IDimensiones
{
float getLargo();
float getAncho();
}

class Caja : IDimensiones
{
float largopulgadas;
float anchopulgadas;

Caja(float largo, float ancho)
{
largopulgadas = largo;
anchopulgadas = ancho;
}
// Implementacion de los miembros de la interface de forma explicita:
float IDimensiones.getLargo()
{
return largopulgadas;
}

float IDimensiones.getAncho()
{
return anchopulgadas;
}

static void Main()
{
// Creando una instancia Caja1 de la clase Caja:
Caja caja1 = new Caja(30.0f, 20.0f);

// Declarando una instancia de la interfaz dimenciones:

IDimensiones dimensiones = (IDimensions)caja1;

// Las siguientes lineas que estan comentadas producirian un error al compilar porque intentan acceder a un miembro de una interface implementada implicitamente desde una instancia de una clase:
//System.Console.WriteLine("Largo: {0}", caja1.getlargo());
//System.Console.WriteLine("Ancho: {0}", caja1.getancho());

// Imprimiendo las dimenciones de la Caja llamando los metodos
// desde una instancia de una interface:

System.Console.WriteLine("Largo: {0}", dimensiones.getLargo());
System.Console.WriteLine("Ancho: {0}", dimensiones.getAncho());
}
}
Resultados
Length: 30
Width: 20
Programación eficaz
Observe que las siguientes líneas, en el método Main, están desactivadas mediante comentarios, ya que producirían errores de compilación. No se puede obtener acceso desde una instancia de clase a un miembro de interfaz implementado explícitamente:

System.Console.WriteLine("Largo: {0}", caja1.getlargo());
//System.Console.WriteLine("Width: {0}", caja1.getancho());
Observe también que las siguientes líneas del método Main imprimen correctamente las dimensiones del cuadro, ya que los métodos se invocan desde una instancia de la interfaz:

System.Console.WriteLine("Largo: {0}", dimensiones.getLarlo());
System.Console.WriteLine("Ancho: {0}", dimensiones.getAncho());

Implementación explícita de interfaz

Si una clase implementa dos interfaces que contienen un miembro con la misma firma, la implementación de ese miembro en la clase hará que ambas interfaces usen ese miembro como implementación. Por ejemplo:

interface IControl
{
void Paint();
}
interface ISurface
{
void Paint();
}
class SampleClass : IControl, ISurface
{
// Ambas ISurface.Paint y IControl.Paint llaman este metodo.
public void Paint()
{
}
}
Sin embargo, los miembros de dos interfaces no realizan la misma función, esto puede llevar a una implementación incorrecta de una o ambas interfaces. Es posible implementar un miembro de interfaz explícitamente, creando un miembro de clase que sólo se llama a través de la interfaz y es específico de ésta. Esto se puede llevar a cabo asignando al miembro de clase el nombre de la interfaz y un punto. Por ejemplo:

public class SampleClass : IControl, ISurface
{
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}
El miembro de clase IControl.Paint sólo está disponible a través de la interfaz IControl, mientras que ISurface.Paint sólo está disponible a través de ISurface. Ambas implementaciones de método son independientes y ninguna está directamente disponible en la clase. Por ejemplo:

SampleClass obj = new SampleClass();
//obj.Paint(); // Error al compilar.

IControl c = (IControl)obj;
c.Paint();
// Llama a IControl.Paint en SampleClass.

ISurface s = (ISurface)obj;
s.Paint(); // Llama a ISurface.Paint en SampleClass.
La implementación explícita también se usa para resolver casos donde cada una de las dos interfaces declara miembros diferentes del mismo nombre como propiedad y método:

interface ILeft
{
int P { get;}
}
interface IRight
{
int P();
}
Para implementar ambas interfaces, una clase tiene que utilizar implementación explícita, ya sea para la propiedad P, el método P o ambos, con el fin de evitar un error del compilador. Por ejemplo:

class Middle : ILeft, IRight
{
public int P() { return 0; }
int ILeft.P { get { return 0; } }
}

viernes, 19 de febrero de 2010

Interfaces

Interfaces
Las interfaces se definen utilizando la palabra clave interface. Por ejemplo:

interface IComparar
{
int CompararCon(object obj);
}
Las interfaces describen un grupo de comportamientos relacionados que pueden pertenecer a cualquier clase o estructura. Las interfaces pueden estar compuestas de métodos, propiedades, eventos, indizadores o cualquier combinación de estos cuatro tipos de miembros. Una interfaz no puede contener campos. Los miembros de interfaz son automáticamente públicos.

Al igual que las clases se pueden heredar de una clase base o estructura, las clases y estructuras se pueden heredar de interfaces, con dos excepciones:

Una clase o estructura puede heredar más de una interfaz.

Cuando una clase o la estructura hereda una interfaz, hereda sólo los nombres de método y las firmas, ya que la propia interfaz no contiene ninguna implementación. Por ejemplo:

public class Minivan : Car, IComparar
{
public int CompararCon(object obj)
{
//implementacion de CompararCon
return 0; //Si la minivan es igual
}
}

Para implementar un miembro de interfaz, el miembro correspondiente de la clase debe ser público, no estático y tener el mismo nombre y la misma firma que el miembro de interfaz. Las propiedades e indizadores de una clase pueden definir descriptores de acceso adicionales para una propiedad o indizador definidos en una interfaz. Por ejemplo, una interfaz puede declarar una propiedad con un descriptor de acceso get, pero la clase que implementa la interfaz puede declarar la misma propiedad con descriptores de acceso get y set. Sin embargo, si la propiedad o el indizador utiliza una implementación explícita, los descriptores de acceso deben coincidir.

Las interfaces y los miembros de interfaz son abstractos; las interfaces no proporcionan una implementación predeterminada. Para obtener más información, vea Clases y miembros de clase abstractos y sellados.

La interfaz IComparar informa al usuario del objeto de que éste se puede comparar con otros objetos del mismo tipo y el usuario de la interfaz no necesita saber cómo se implementa.

Las interfaces pueden heredar otras interfaces. Es posible que una clase herede una interfaz varias veces, a través de las clases base o interfaces que hereda. En ese caso, la clase sólo puede implementar la interfaz una vez, siempre que ésta se declare como parte de la nueva clase. Si la interfaz heredada no está declarada como parte de la nueva clase, la clase base que la declaró proporcionará su implementación. Es posible que una clase base implemente miembros de interfaz a través de miembros virtuales. En ese caso, la clase que hereda la interfaz puede cambiar el comportamiento de la interfaz reemplazando los miembros virtuales. Para obtener más información sobre miembros virtuales, vea Polimorfismo.

Información general sobre interfaces
Una interfaz tiene las siguientes propiedades:

Una interfaz es similar a una clase base abstracta. Cualquier tipo no abstracto que hereda la interfaz debe implementar todos sus miembros.

No se pueden crear instancias directamente de una interfaz.

Las interfaces pueden contener eventos, métodos, indizadores y propiedades.

Las interfaces no contienen implementaciones de métodos.

Las clases y estructuras se pueden heredar de más de una interfaz.

Una interfaz se puede heredar de varias interfaces.

Polimorfismo

Polimorfismo

A través de la herencia, una clase puede utilizarse como más de un tipo; puede utilizarse como su propio tipo, cualquier tipo base o cualquier tipo de interfaz si implementa interfaces. Esto se denomina polimorfismo. En C#, todos los tipos son polimórficos. Los tipos se pueden utilizar como su propio tipo o como una instancia de Object, porque cualquier tipo trata automáticamente a Object como tipo base.

El polimorfismo no sólo es importante para las clases derivadas, sino también para las clases base. Cualquiera que utilice la clase base podría, de hecho, estar utilizando un objeto de la clase derivada que se haya convertido en el tipo de clase base. Los diseñadores de una clase base pueden anticipar qué aspectos de su clase base cambiarán probablemente para un tipo derivado. Por ejemplo, es posible que una clase base para automóviles contenga un comportamiento sujeto a cambios si el automóvil en cuestión es un vehículo familiar o uno descapotable. Una clase base puede marcar esos miembros de clase como virtuales, lo cual permite que las clases derivadas que representan automóviles descapotables y vehículos familiares reemplacen ese comportamiento.

Para obtener más información, vea Herencia.

Información general sobre el polimorfismo
Cuando una clase derivada hereda de una clase base, obtiene todos los métodos, campos, propiedades y eventos de la clase base. Para cambiar los datos y el comportamiento de una clase base, existen dos opciones: se puede reemplazar el miembro base por un nuevo miembro derivado o se puede reemplazar un miembro base virtual.

Para reemplazar un miembro de una clase base por un nuevo miembro derivado, se requiere la palabra clave new. Si una clase base define un método, campo o propiedad, la palabra clave new se utiliza para crear una nueva definición de ese método, campo o propiedad en una clase derivada. La palabra clave new se coloca antes del tipo de valor devuelto de un miembro de clase que se reemplaza. Por ejemplo:

public class ClaseBase
{
public void DoWork() { }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}

public class ClaseDerivada : ClaseBase
{
public new void DoWork() { }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}


Cuando se utiliza la palabra clave new, se llama a los nuevos miembros de clase en lugar de los miembros de clase base que se han reemplazado. Esos miembros de clase base se denominan miembros ocultos. Aún es posible llamar a los miembros de clase ocultos si una instancia de la clase derivada se convierte en una instancia de la clase base. Por ejemplo:

ClaseDerivada B = new ClaseDerivada();
B.DoWork(); // Llama el metodo nuevo.

ClaseBase A = (ClaseBase)B;
A.DoWork(); // Llama el metodo viejo.


Para que una instancia de una clase derivada controle por completo un miembro de clase de una clase base, la clase base debe declarar ese miembro como virtual. Esto se consigue agregando la palabra clave virtual antes del tipo de valor devuelto del miembro. Una clase derivada tiene entonces la opción de utilizar la palabra clave override, en lugar de new, para reemplazar la implementación de la clase base por la propia. Por ejemplo:

public class ClaseBase
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class ClaseDerivada : ClaseBase
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}


Los campos no pueden ser virtuales. Sólo los métodos, propiedades, eventos e indizadores pueden serlo. Cuando una clase derivada reemplaza un miembro virtual, se llama a ese miembro aunque se tenga acceso a una instancia de esa clase como instancia de la clase base. Por ejemplo:

ClaseDerivada B = new ClaseDerivada();
B.DoWork(); // Llama el metodo nuevo.

ClaseBase A = (ClaseBase)B;
A.DoWork(); // Tambien llama el metodo nuevo.


Los métodos y propiedades virtuales permiten hacer planes para una expansión futura. El hecho de llamar a un miembro virtual sin importar cuál es el tipo que el llamador utiliza proporciona a las clases derivadas la opción de cambiar completamente el comportamiento aparente de la clase base.

Los miembros virtuales siguen siendo virtuales de forma indefinida, independientemente de cuántas clases se hayan declarado en la clase que originalmente declaró el miembro virtual. Si la clase A declara un miembro virtual, la clase B deriva de A y la clase C deriva de B, la clase C hereda el miembro virtual y tiene la opción de reemplazarlo, independientemente de si la clase B declaró la sustitución de ese miembro. Por ejemplo:

public class A
{
public virtual void DoWork() { }
}

public class B : A
{
public override void DoWork() { }
}


public class C : B
{
public override void DoWork() { }
}


Una clase derivada puede detener la herencia virtual si la sustitución se declara como sellada. Para ello es necesario colocar la palabra clave sealed antes de la palabra clave override en la declaración del miembro de clase. Por ejemplo:

public class C : B
{
public sealed override void DoWork() { }
}


En el ejemplo anterior, el método DoWork ya no es virtual para ninguna clase derivada de C. Todavía es virtual para instancias de C, aunque se conviertan al tipo B o A. Los métodos sellados se pueden reemplazar por clases derivadas mediante la palabra clave new, como se muestra en el ejemplo siguiente:

public class D : C
{
public new void DoWork() { }
}


En este caso, si se llama a DoWork en D mediante una variable de tipo D, se llama al nuevo DoWork. Si se utiliza una variable de tipo C, B o A para tener acceso a una instancia de D, una llamada a DoWork seguirá las reglas de la herencia virtual y enrutará esas llamadas a la implementación de DoWork en la clase C.

Una clase derivada que ha reemplazado un método o propiedad todavía puede tener acceso al método o propiedad de la clase base utilizando la palabra clave base. Por ejemplo:

public class A
{
public virtual void DoWork() { }
}

public class B : A
{
public override void DoWork() { }
}


public class C : B
{
public override void DoWork()
{
// Llama a DoWork en B para obtener el comportamiento de B:
base.DoWork();

// El comportamiento de DoWork que especifica a C iria aqui:
// ...
}
}


Para obtener más información, vea base.

viernes, 12 de febrero de 2010

Clases y miembros de clase abstractos y sellados

Clases y miembros de clase abstractos y sellados La palabra clave abstract permite crear clases y miembros de clase que están incompletos y se deben implementar en una clase derivada.

La palabra clave sealed permite impedir la herencia de una clase o de ciertos miembros de clase marcados previamente como virtuales.

Clases y miembros de clase abstractos Las clases se pueden declarar como abstractas si se incluye la palabra clave abstract antes de la definición de clase. Por ejemplo:

public abstract class A
{
    // Miembros de la clase aqui.
}

No se pueden crear instancias de una clase abstracta. El propósito de una clase abstracta es proporcionar una definición común de una clase base que múltiples clases derivadas pueden compartir. Por ejemplo, una biblioteca de clase puede definir una clase abstracta que se utiliza como parámetro para muchas de sus funciones y solicitar a los programadores que utilizan esa biblioteca que proporcionen su propia implementación de la clase mediante la creación de una clase derivada.

Las clases abstractas también pueden definir métodos abstractos. Esto se consigue agregando la palabra clave abstract antes del tipo de valor que devuelve el método. Por ejemplo:

public abstract class A
{
    public abstract void DoWork(int i);
}

Los métodos abstractos no tienen ninguna implementación, de modo que la definición de método va seguida por un punto y coma en lugar de un bloque de método normal. Las clases derivadas de la clase abstracta deben implementar todos los métodos abstractos. Cuando una clase abstracta hereda un método virtual de una clase base, la clase abstracta puede reemplazar el método virtual con un método abstracto. Por ejemplo:

public class D { public virtual void DoWork(int i) { // Implementacion Original. } } public abstract class E : D { public abstract override void DoWork(int i); } public class F : E { public override void DoWork(int i) { // Nueva Implementacion } }
Si un método virtual se declara como abstract, sigue siendo virtual para cualquier clase que herede de la clase abstracta. Una clase que hereda un método abstracto no puede tener acceso a la implementación original del método; en el ejemplo anterior, DoWork de la clase F no puede llamar a DoWork de la clase D. De esta forma, una clase abstracta puede obligar a las clases derivadas a proporcionar nuevas implementaciones de método para los métodos virtuales.

Clases y miembros de clase sellados Las clases se pueden declarar como selladas si se incluye la palabra clave sealed antes de la definición de clase. Por ejemplo:
public sealed class D
{
    // Miembros de la clase aqui.
}

Una clase sellada no se puede utilizar como clase base. Por esta razón, tampoco puede ser una clase abstracta. Las clases selladas evitan la derivación. Puesto que nunca se pueden utilizar como una clase base, algunas optimizaciones en tiempo de ejecución pueden hacer que sea un poco más rápido llamar a miembros de clase sellada.

Un miembro de clase, método, campo, propiedad o evento de una clase derivada que reemplaza a un miembro virtual de la clase base puede declarar ese miembro como sellado. Esto niega el aspecto virtual del miembro para cualquier clase derivada adicional. Esto se logra colocando la palabra clave sealed antes de la palabra clave override en la declaración del miembro de clase. Por ejemplo:

public class D : C
{
    public sealed override void DoWork() { }
}

base

Como se que el uso de la palabra base en C# para heredar tiende a traer diversas controversias, aquí muestro una muy buena información acerca de base y de algunas formas de como utilizarla, quizas diferente a como la he dado en clases de la universidad.
base

La palabra clave base se utiliza para obtener acceso a los miembros de la clase base desde una clase derivada:

  • Realice una llamada a un método de la clase base reemplazado por otro método.

  • Especifique a qué constructor de la clase base se debe llamar para crear instancias de la clase derivada.

El acceso a una clase base sólo se permite en un constructor, en un método de instancia o en un descriptor de acceso a una propiedad de instancia.

Es incorrecto utilizar la palabra clave base desde dentro de un método estático.

Ejemplo:

En este ejemplo, tanto la clase base, Persona, como la clase derivada, Empleado, poseen un método denominado VerInformacion. Mediante la palabra clave base, se puede realizar una llamada al método VerInformacion de la clase base desde la clase derivada.


// Accediendo a miembos de una clase base
using System;
public class Persona
{
    protected string cedula = "001-12345-9";
    protected string nombre = "Jonathan Nunez";

    public virtual void VerInformacion()
    {
        Console.WriteLine("Nombre: {0}", nombre);
        Console.WriteLine("Cedula: {0}", cedula);
    }
}
class Empleado : Persona
{
    public string id = "JN123456";
    public override void VerInformacion()
    {
        // Llamando el metodo VerInformacion desde la clase base:
        base.VerInformacion();
        Console.WriteLine("Empeado ID: {0}", id);
    }
}

class ClassPrueba
{
    static void Main()
    {
        Empleado E = new Empleado();
        E.VerInformacion();
    }
}

Este ejemplo muestra cómo especificar el constructor de la clase base al que se realiza la llamada cuando se crean instancias de una clase derivada.

using System;
public class ClaseBase
{
    int num;
    public ClaseBase()
    {
        Console.WriteLine("Dentro de la Clase Base()");
    }

    public ClaseBase(int i)
    {
        num = i;
        Console.WriteLine("Dentro de la Clase Base con parametro(int i)");
    }

    public int VerNum()
    {
        return num;
    }
}

public class ClaseDerivada : ClaseBase
{
    // El constructor llamara la ClaseBase.ClaseBase()
    public ClaseDerivada() : base()
    {
    }

    // Este constructor llamara la ClaseBase.ClaseBase(int i)
    public ClaseDerivada(int i) : base(i)
    {
    }

    static void Main()
    {
        ClaseDerivada cd = new ClaseDerivada();
        ClaseDerivada cd1 = new ClaseDerivada(1);
    }
}
Resultados
Nombre: Jonathan Nunez
Cedula: 001-12345-9
Empleado ID: JN123456
Espero desde estos ejemplos usando base puedan entender lo suficiente como para meter mano con ello. Si desean mas informacion sobre virtual u override luego posteare sobre ello con algunos ejemplos quizas.

Herencia

La herencia, junto con la encapsulación y el polimorfismo, es una de las tres características principales (o "pilares") de la programación orientada a objetos. La herencia permite crear nuevas clases que reutilizan, extienden y modifican el comportamiento que se define en otras clases. La clase cuyos miembros se heredan se denomina clase base y la clase que hereda esos miembros se denomina clase derivada.

Nota

Las estructuras no admiten la herencia, pero pueden implementar interfaces.


Conceptualmente, una clase derivada es una especialización de la clase base. Por ejemplo, si tiene una clase base Animal, puede tener una clase derivada denominada Mammal y otra clase derivada denominada Reptile. Mammal es Animaly Reptile es Animal, pero cada clase derivada representa especializaciones diferentes de la clase base. Al definir una clase para derivar de otra clase, la clase derivada obtiene implícitamente todos los miembros de la clase base, salvo sus constructores y destructores. La clase derivada puede, por tanto, reutilizar el código de la clase base sin tener que volver a implementarlo. En la clase derivada, puede agregar más miembros. De esta manera, la clase derivada extiende la funcionalidad de la clase base. En la ilustración siguiente se muestra una clase ArticuloTrabajo que representa un elemento de trabajo en algún proceso de negocio. Como todas las clases, deriva de System..::.Object y hereda todos sus métodos. ArticuloTrabajo agrega cinco miembros propios. Esto incluye un constructor, porque los constructores no se heredan. PedirCambio hereda de ArticuloTrabajo y representa un tipo determinado de elemento de trabajo. PedirCambio agrega dos miembros más a los miembros que hereda de ArticuloTrabajo y Object. Debe agregar su propio constructor y también agrega un miembro que permitirá asociar PedirCambio al elemento ArticuloTrabajo  original al que se aplica el cambio.


En el ejemplo siguiente se muestra cómo se expresan en C# las relaciones de clase presentadas en la ilustración anterior. En el ejemplo también se muestra cómo ArticuloTrabajo invalida el método virtual Object..::.ToString y cómo la clase PedirCambio hereda la implementación de ArticuloTrabajo del método.
// ArituloTrabajo implícitamente hereda de la clase Object.
public class ArticuloTrabajo
{
    // El campo estático IDactual almacena el ID de trabajo del último ArticuloTrabajo que
    // ha sido creado.
    private static int IDactual;

    //Propiedades.
    protected int ID { get; set; }
    protected string Titulo { get; set; }
    protected string Descripcion { get; set; }
    protected TimeSpan TiempoTrabajo { get; set; }

    // Constructor predeterminado. Si una clase derivada no invoca una clase-
    // base constructor explícitamente, el constructor predeterminado se llama
    // implícitamente.
    public ArticuloTrabajo()
    {
        ID = 0;
        Titulo = "Título predeterminado";
        Descripcion = "Descripción predeterminada.";
        tiempoTrabajo = new TimeSpan();
    }

    // Constructor de instancias que tiene tres parámetros.
    public ArticuloTrabajo(string titulo, string desc, TimeSpan tiempotrabajo)
    {
        this.ID = GetNextID();
        this.Titulo = titulo;
        this.Description = desc;
        this.TiempoTrabajo = tiempotrabajo;
    }

    // Constructor estático para inicializar el miembro estático, IDactual. Esto
    // El constructor se llama una vez, automáticamente, antes que cualquier instancia.
    // de ArticuloTrabajo o PedirCambio se crea, o se hace referencia a IDactual.
    static ArticuloTrabajo() => IDactual = 0;

    // IDactual es un campo estático. Se incrementa cada vez que una nueva
    // instancia de ArticuloTrabajo es creada.
    protected int GetNextID() => ++IDactual;

    // Método Actualizar le permite actualizar el título y la duración del trabajo de un
    // existente objeto de tipo ArticuloTrabajo.
    public void Actualizar(string titulo, TimeSpan tiempotrabajo)
    {
        this.Titulo = titulo;
        this.TiempoTrabajo = tiempotrabajo;
    }

    // Anulación del método virtual del método ToString que se hereda
     // de System.Object.
    public override string ToString() =>
        $"{this.ID} - {this.Titulo}";
}

// PedirCambio deriva de ArticuloTrabajo y agrega una propiedad (originalItemID)
// y dos constructores.
public class Pedircambio: ArticuloTrabajo
{
    protected int originalItemID { get; set; }

    // Constructores. Porque ninguno de los constructores llama a una clase base
     // constructor explícitamente, el constructor predeterminado en la clase base
     // se llama implícitamente. La clase base debe contener un valor predeterminado
     // constructor.

   // Constructor predeterminado para la clase derivada.
    public Pedircambio() { }

    // Constructor de instancia que tiene cuatro parámetros.
    public Pedircambio(string titulo, string desc, TimeSpan tiempotrabajo,
                         int originalID)
    {
        // Las siguientes propiedades y el método GetNexID se heredan
         // de ArticuloTrabajo.
        this.ID = GetNextID();
        this.Title = title;
        this.Descripcion = desc;
        this.TiempoTrabajo = tiempotrabajo;

        // La propiedad originalItemId es miembro de PedirCambio, pero no
         // de ArticuloTrabajo.
        this.originalItemID = originalID;
    }
}

El siguiente bloque muestra cómo utilizar las clases base y derivadas:


// Crea una instancia de ArticuloTrabajo usando el constructor en el
// clase base que toma tres argumentos.
ArticuloTrabajo item = new ArticuloTrabajo("Corregir errores",
                            "Corregir todos los errores en mi código",
                            new TimeSpan(3, 4, 0, 0));

// Crea una instancia de PedirCambio usando el constructor en
// la clase derivada que toma cuatro argumentos.
Pedircambio cambio = new Pedircambio("Cambiar el diseño de la clase base",
                                        "Agregar miembros a la clase",
                                        new TimeSpan(4, 0, 0),
                                        1);

// Usa el método ToString definido en ArticuloTrabajo.
Console.WriteLine(item.ToString());

// Utilice el método de actualización heredado para cambiar el título del
// Objeto PedirCambio.
cambio.Actualizar("Cambiar el diseño de la clase base",
    new TimeSpan(4, 0, 0));

// PedirCambio hereda la sobrescritura de ToString desde ArticuloTrabajo
Console.WriteLine(cambio.ToString());
/* Salida:
    1 - Corregir errores
    2 - Cambiar el diseño de la clase base
*/


Métodos abstractos y virtuales Cuando una clase base declara un método como virtual, una clase derivada puede invalidar el método con su propia implementación. Si una clase base declara un miembro como abstracto, ese método se debe invalidar en cualquier clase no abstracta que herede directamente de dicha clase. Si una clase derivada es abstracta en sí misma, hereda los miembros abstractos sin implementarlos. Los miembros abstractos y virtuales son la base para el polimorfismo, la segunda característica principal de la programación orientada a objetos. Para obtener más información, vea Polimorfismo

Clases base abstractas Puede declarar una clase como abstracta si desea evitar la creación directa de instancias por medio de la palabra clave new. Si hace esto, la clase solo se puede utilizar si una nueva clase se deriva de ella. Una clase abstracta puede contener una o más firmas de método que se declaran a sí mismas como abstractas. Estas firmas especifican los parámetros y el valor devuelto pero no tienen ninguna implementación (cuerpo del método). Una clase abstracta no tiene que contener miembros abstractos; sin embargo, si una clase contiene un miembro abstracto, la propia clase se debe declarar como abstracta. Las clases derivadas que no son abstractas por sí mismas deben proporcionar la implementación de cualquier método abstracto de una clase base abstracta. Para obtener más información, vea Clases y miembros de clase abstractos y sellados y Diseño de clases abstractas. 

Interfaces Una interfaz es un tipo de referencia similar en cierto modo a una clase base abstracta compuesta únicamente por miembros abstractos. Cuando una clase deriva de una interfaz, debe proporcionar una implementación para todos los miembros de la interfaz. Una clase puede implementar varias interfaces aunque solo puede derivar de una única clase base directa. Las interfaces se utilizan para definir funciones específicas para las clases que no tienen necesariamente una relación de identidad. Por ejemplo, cualquier clase o estructura que tenga que habilitar el código de cliente para determinar si dos objetos de un tipo son equivalentes (cualquiera que sea la forma en que el tipo defina la equivalencia), puede implementar la interfaz IEquatable[`1]. IEquatable (T) no implica el mismo tipo de relación de identidad que existe entre una clase base y una clase derivada (por ejemplo, Mammal es Animal). Para obtener más información, vea Interfaces

Acceso de la clase derivada a los miembros de la clase base Una clase derivada tiene acceso a los miembros públicos, protegidos, internos e internos protegidos de una clase base. Aunque una clase derivada hereda los miembros privados de una clase base, no puede tener acceso a estos miembros. Sin embargo, todos los miembros privados siguen presentes en la clase derivada y pueden hacer el mismo trabajo que harían en la propia clase base. Por ejemplo, supongamos que un método protegido de la clase base tiene acceso a un campo privado. Este campo debe estar presente en la clase derivada para que el método heredado de la clase base funcione correctamente. 

Evitar la derivación adicional Una clase puede evitar que otras clases hereden de ella, o de cualquiera de sus miembros, declarándose a sí misma o al miembro como sealed. Para obtener más información, vea Clases y miembros de clase abstractos y sellados

Ocultar miembros de la clase base en la clase derivada Una clase derivada puede ocultar miembros de la clase base si los declara con el mismo nombre y firma. Se puede utilizar el modificador new para indicar explícitamente que no se pretende que el miembro sea una invalidación del miembro base. No es necesario utilizar new, pero se generará una advertencia del compilador si no se usa new.