InicioArticulos y noticiasBases de datosProgramaciónForosInternetServiciosEmail
Tutorial C#
Introduccion a C#
Programacion con C#
Fundamentos de C#
Operadores
Instrucciones
Estrcuturas de control.
Definición de clases
Creación de objetos
Herencia y métodos virtuales
Polimorfismo
Ocultación de miembros
Miembros de tipo
Encapsulación
Espacios de nombres
Importación de espacios de nombres
Espacio de nombres distribuidos
Variables y tipos de datos
Tablas unidimensionales
Tablas multidimensionales
La clase System.Array
Cadenas de texto
Constantes
Orden de inicialización de variables
Métodos
Métodos externos
Constructores
Destructores
Propiedades
Indizadores
Redefinición de operadores
Delegados y eventos
La clase MulticastDelegate
Llamadas asíncronas
Implementación interna de los delegados
Eventos
Estructuras
Boxing y unboxing
Constructores de estructuras
Enumeraciones
Interfaces
Excepciones
Otras instrucciones
Atributos
Definición de nuevos atributos
Lectura de atributos en tiempo de ejecución
Atributos de compilación
Pseudoatributos
Código inseguro
Definición de punteros
Manipulación de punteros
Operadores relacionados con código inseguro
Fijación de variables apuntadas
Novedades de C# 2.0
Genéricos
Tipos parciales
Iteradores
Mejoras en la manipulación de delegados
Tipos anulables
Modificadores de visibilidad de bloques get y set
Clases estáticas
Referencias a espacios de nombres
Supresión temporal de avisos
Atributos condicionales
Incrustación de tablas en estructuras
Modificaciones en el compilador
También puedes ver ...
LinQ To SQL - Un ejemplo sencillo
Problemas en la instalación de Visual Studio 2008-Visual studio web authoring component
Redimiento de VS2008
Insertar en una Tabla usando Linq2SQL (Linq To SQL)
Primeros pasos con Linq2SQL (Linq To SQL)
Novedades Visual Studio 2008 DataSet en 3 capas o MVC
Novedades en C# 3.0 - Primeros pasos con LINQ
Primeras impresiones sobre Visual Studio 2008
Desencriptar Texto y variables con .NET VB y C# (segunda parte)
¿Que son IPCChannel o el protocolo IPC en WCF?

Afiliados
La Web del programador
MundoProgramacion


Inicio | Tutorial C# Herencia y métodos virtualesTutorial C#Ocultación de miembros Versión para imprimir

Polimorfismo

Concepto de polimorfismo

    El polimorfismo es otro de los pilares fundamentales de la programación orientada a objetos. Es la capacidad de almacenar objetos de un determinado tipo en variables de tipos antecesores del primero a costa, claro está, de sólo poderse acceder a través de dicha variable a los miembros comunes a ambos tipos. Sin embargo, las versiones de los métodos virtuales a las que se llamaría a través de esas variables no serían las definidas como miembros del tipo de dichas variables, sino las definidas en el verdadero tipo de los objetos que almacenan.

    A continuación se muestra un ejemplo de cómo una variable de tipo Persona puede usarse para almacenar objetos de tipo Trabajador. En esos casos el campo Sueldo del objeto referenciado por la variable no será accesible, y la versión del método Cumpleaños() a la que se podría llamar a través de la variable de tipo Persona sería la definida en la clase Trabajador, y no la definida en Persona:


using System;
class Persona
{
 // Campo de cada objeto Persona que almacena su nombre
 public string Nombre;     
 // Campo de cada objeto Persona que almacena su edad
 public int Edad;              
 // Campo de cada objeto Persona que almacena su NIF
 public string NIF;            
 
 // Incrementa en uno la edad del objeto Persona
 public virtual void Cumpleaños()
 {
   Console.WriteLine("Incrementada edad de persona");
 }
 
 // Constructor de Persona
 public Persona (string nombre, int edad, string nif)
 {
   Nombre = nombre;
   Edad = edad;
   NIF = nif;
 }
}

class Trabajador: Persona
{
 // Campo de cada objeto Trabajador que almacena cuánto gana
 int Sueldo;
 
 Trabajador(string nombre, int edad, string nif, int sueldo)
     : base(nombre, edad, nif)
 {// Inicializamos cada Trabajador en base al constructor de Persona
   Sueldo = sueldo;
 }
 
 public override Cumpleaños()
 {
   Edad++;
   Console.WriteLine("Incrementada edad de trabajador");
 }
 
 public static void Main()
 {
   Persona p = new Trabajador("Josan", 22, "77588260-Z", 100000);
   p.Cumpleaños();     
   // p.Sueldo++; //ERROR: Sueldo no es miembro de Persona
 }
}

    El mensaje mostrado por pantalla al ejecutar este método confirma lo antes dicho respecto a que la versión de Cumpleaños() a la que se llama, ya que es:

 

 Incrementada edad de trabajador

Métodos genéricos

El polimorfismo es muy útil ya que permite escribir métodos genéricos que puedan recibir parámetros que sean de un determinado tipo o de cualquiera  de sus tipos hijos. Es más, en tanto que cómo se verá en el epígrafe siguiente, en C# todos los tipos derivan implícitamente del tipo System.Object, podemos escribir métodos que admitan parámetros de cualquier tipo sin más que definirlos como métodos que tomen parámetros de tipo System.Object. Por ejemplo:


public void MétodoGenérico(object o)
{
  // Código del método
}

    Nótese que en vez de System.Object se ha escrito object, que es el nombre abreviado incluido en C# para hacer referencia de manera compacta a un tipo tan frecuentemente usado como System.Object.

Determinación de tipo. Operador is

Dentro de una rutina polimórifica que, como la del ejemplo anterior, admita parámetros que puedan ser de cualquier tipo, muchas veces es conveniente poder consultar en el código de la misma cuál es el tipo en concreto del parámetro que se haya pasado al método en cada llamada al mismo. Para ello C# ofrece el operador is, cuya forma sintaxis de uso es:


<expresión> is <nombreTipo>

Este operador devuelve true en caso de que el resultado de evaluar <expresión> sea del tipo cuyo nombre es <nombreTipo> y false en caso contrario[8]. Gracias a ellas podemos escribir métodos genéricos que puedan determinar cuál es el tipo que tienen los parámetros que en cada llamada en concreto se les pasen. O sea, métodos como:


public void MétodoGenérico(object o)
{
 if  (o is int)// Si o es de tipo int (entero)...
               //...Código a ejecutar si el objeto o es de tipo int
 else if (o is string) // Si no, si o es de tipo string (cadena)...
                       //...Código a ejecutar si o es de tipo string
 //... Ídem para otros tipos
}

    El bloque if...else es una instrucción condicional que permite ejecutar un código u otro en función de si la condición indicada entre paréntesis tras el if es cierta (true) o no (false) Esta instrucción se explicará más detalladamente en el Tema 16: Instrucciones

Acceso a la clase base

    Hay determinadas circunstancias en las que cuando redefinamos un determinado método nos interese poder acceder al código de la versión original. Por ejemplo, porque el código redefinido que vayamos a escribir haga lo mismo que el original y además algunas cosas extras. En estos casos se podría pensar que una forma de conseguir esto sería convirtiendo el objeto actual al tipo del método a redefinir y entonces llamar así a ese método, como por ejemplo en el siguiente código:


using System;
class A
{
 public virtual void F()
 {
    Console.WriteLine("A");
 }
}
class B:A
{
 public override void F()
 {
    Console.WriteLine("Antes");
     ((A) this).F();   // (2)
     Console.WriteLine("Después");
 }
 public static void Main()
 {
     B b = new B();
     b.F();
 }
}

Pues bien, si ejecutamos el código anterior veremos que la aplicación nunca termina de ejecutarse y está constantemente mostrando el mensaje Antes por pantalla. Esto se debe a que debido al polimorfismo se ha entrado en un bucle infinito: aunque usemos el operador de conversión para tratar el objeto como si fuese de tipo A, su verdadero tipo sigue siendo B, por lo que la versión de F() a la que se llamará en (2) es a la de B de nuevo, que volverá a llamarse así misma una y otra vez de manera indefinida.

Para solucionar esto, los diseñadores de C# han incluido una palabra reservada llamada base que devuelve una referencia al objeto actual semejante a this pero con la peculiaridad de que los accesos a ella son tratados como si el verdadero tipo fuese el de su clase base. Usando base, podríamos reemplazar el código de la redefinición de F() de ejemplo anterior por:


public override void F()
{
    Console.WriteLine("Antes");
    base.F();
    Console.WriteLine("Después");
}

Si ahora ejecutamos el programa veremos que ahora sí que la versión de F() en B llama a la versión de F() en A, resultando la siguiente salida por pantalla:

 Antes
 A
 Después 

         

A la hora de redefinir métodos abstractos hay que tener cuidado con una cosa: desde el método redefinidor no es posible usar base para hacer referencia a métodos abstractos de la clase padre, aunque sí para hacer referencia a los no abstractos. Por ejemplo:


abstract class A
{
 public abstract void F();
 public void G(){}
}
class B: A
{
 public override void F() 
 {
     base.G();// Correcto
     base.F();// Error, base.F() es abstracto
 }
}

Downcasting

Dado que una variable de un determinado tipo puede estar en realidad almacenando un objeto que sea de algún tipo hijo del tipo de la variable y en ese caso a través de la variable sólo puede accederse a aquellos miembros del verdadero tipo del objeto que sean comunes con miembros del tipo de la variable que referencia al objeto, muchas veces nos va a interesar que una vez que dentro de un método genérico hayamos determinado cuál es el verdadero tipo de un objeto (por ejemplo, con el operador is) podamos tratarlo como tal. En estos casos lo que hay es que hacer una conversión del tipo padre al verdadero tipo del objeto, y a esto se le llama downcasting

Para realizar un downcasting una primera posibilidad es indicar preceder la expresión a convertir del tipo en el que se la desea convertir indicado entre paréntesis. Es decir, siguiendo la siguiente sintaxis:


(
<tipoDestino>) <expresiónAConvertir>

El resultado de este tipo de expresión es el objeto resultante de convertir el resultado de <expresiónAConvertir> a <tipoDestino>. En caso de que la conversión no se pudiese realizar se lanzaría una excepción del tipo predefinido System.InvalidCastException

Otra forma de realizar el downcasting es usando el operador as, que se usa así:


<expresiónAConvertir> as <tipoDestino>

    La principal diferencia de este operador con el anterior es que si ahora la conversión no se pudiese realizar se devolvería null en lugar de lanzarse una excepción. La otra diferencia es que as sólo es aplicable a tipos referencia y sólo a conversiones entre tipos de una misma jerarquía (de padres a hijos o viceversa)

    Los errores al realizar conversiones de este tipo en métodos genéricos se producen cuando el valor pasado a la variable genérica no es ni del tipo indicado en <tipoDestino> ni existe ninguna definición de cómo realizar la conversión a ese tipo (cómo definirla se verá en el Tema 11: Redefinición de operadores)

Clases y métodos sellados

    Una clase sellada es una clase que no puede tener clases hijas, y para definirla basta anteponer el modificador sealed a la definición de una clase normal. Por ejemplo:


sealed class ClaseSellada
{}

    Una utilidad de definir una clase como sellada es que permite que las llamadas a sus métodos virtuales heredados se realicen tan eficientemente como si fuesen no virtuales, pues al no poder existir clases hijas que los redefinan no puede haber polimorfismo y no hay que determinar cuál es la versión correcta del método a la que se ha de llamar.  Nótese que se ha dicho métodos virtuales heredados, pues lo que no se permite es definir miembros virtuales dentro de este tipo de clases, ya que al no poderse heredarse de ellas es algo sin sentido en tanto que nunca podrían redefinirse.

    Ahora bien, hay que tener en cuenta que sellar reduce enormemente su capacidad de reutilización, y eso es algo que el aumento de eficiencia obtenido en las llamadas a sus métodos virtuales no suele compensar. En realidad la principal causa de la inclusión de estas clases en C# es que permiten asegurar que ciertas clases críticas nunca podrán tener clases hijas y sus variables siempre almacenarán objetos del mismo tipo. Por ejemplo, para simplificar el funcionamiento del CLR y los compiladores se ha optado por hacer que todos los tipos de datos básicos excepto System.Object estén sellados.

     Téngase en cuenta que es absurdo definir simultáneamente una clase como abstract y sealed, pues nunca podría accederse a la misma al no poderse crear clases hijas suyas que definan sus métodos abstractos. Por esta razón, el compilador considera erróneo definir una clase con ambos modificadores a la vez.

     Aparte de para sellar clases, también se puede usar sealed como modificador en la redefinición de un método para conseguir que la nueva versión del mismo que se defina deje de ser virtual y se le puedan aplicar las optimizaciones arriba comentadas. Un ejemplo de esto es el siguiente:


class A
{
    public abstract F();
}

class B:A
{
    public sealed override F() // F() deja de ser redefinible
    {}
}


Inicio | Tutorial C# Herencia y métodos virtualesTutorial C#Ocultación de miembros Versión para imprimir Foros de consulta


Añadir comentario ... Para preguntar utiliza los foros
Autor:

Título:


Para preguntar utiliza los foros.



Inicio | Tutorial C# Herencia y métodos virtualesTutorial C#Ocultación de miembros Versión para imprimir

Polimorfismo
Autor: José Antonio González Seco
Visitas: 15880 Fecha de publicación: 03/10/2006
José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.



devjoker  Lo más nuevo ... Donde se habla ... 
03/04/2008 LINQ con métodos Extensores y expresiones lambda    forma parte de...Métodos extensores
03/04/2008 Métodos Extensores: SELECT    forma parte de...Métodos extensores
03/04/2008 Almacenamiento (1)    forma parte de...Tutorial de Sistemas Informaticos
28/03/2008 Mapa de configuracion de WCF
28/03/2008 LinQ To SQL - Un ejemplo sencillo
25/03/2008 Sitios Web de ejemplo ASP.NET
22/03/2008 Problemas al conectar a redes wifi con Windows Vista
19/03/2008 Service Pack 1 de Windows Vista

Útimos temas recibidos en los foros ...
Insertar datos en un grid manualmente por flakita ... [C#] 0 09/04/2008
ayuda con un select por Manu ... [mySQL] 8 25/07/2007
CHASIS DE UN PC CUALQUIERA por carolina ... [WIN 98/NT/2000/XP] 5 23/11/2006
Q PASA JENTE por torrichi ... [Java] 2 25/03/2008
LLINQ to SQL por Javier ... [C#] 1 09/04/2008
Agendar tarea por dia pero q pueda ser a diferente hora cada dia por Orlan ... [C#] 1 09/04/2008
Ventana Popup por Maurito22 ... [ASP.NET] 1 31/03/2008
No se puede abrir el archivo fisico por ron ... [C#] 1 07/04/2008
Data report visual basic 6.0 impresion con codigo por Andres ... [Visual Basic 6.0] 8 19/11/2007
Como abrir un docuemto de word desde c# y leer las palabras para luego enviar a access por llen ... [C#] 1 08/04/2008
sentencia for por .net ... [C#] 2 08/04/2008
Leer campos blob de oracle en lenguaje visual basic por janito ... [Visual Basic 6.0] 0 08/04/2008

Access CGI JSP ORACLE UNIX
Actualidad HTML/DHTML/XHTML LINUX PHP Visual Basic .NET
ASP ISAPI MS DOS Power Builder Visual Basic 6.0
ASP.NET Java mySQL SQL WIN 98/NT/2000/XP
C# JavaScript Opinion SQL Server

devjoker  Te recomendamos además ...
21/07/2006 Funciones integradas de PL/SQL    forma parte de...Tutorial PL/SQL
28/08/2006 Ubuntu en VmWare con VMware Tools
22/10/2005 Insertar datos. INSERT    forma parte de...Tutorial SQL
24/05/2007 Tipos de datos en Transact SQL    forma parte de...Tutorial de Transact SQL
12/01/2006 Programación con PL/SQL    forma parte de...Tutorial PL/SQL
16/10/2006 Delegados y eventos    forma parte de...Tutorial C#
14/05/2007 Fundamentos de Transact SQL    forma parte de...Tutorial de Transact SQL
31/03/2006 Operadores en PL/SQL    forma parte de...Tutorial PL/SQL
11/09/2006 Trabajar con datos de tipo BLOB en ORACLE
12/01/2006 Introducción a PLSQL    forma parte de...Tutorial PL/SQL

 

Encuesta
¿Cual es tu lenguaje de programación favorito?
[Ver] [Votar]