En la lección anterior definiste la clase Libro con sus propiedades (Titulo, Autor,
Isbn, Disponible), pero un objeto Libro era, de momento, un contenedor pasivo de datos:
para prestarlo había que escribir código externo que pusiera Disponible a false a mano, y
para mostrar su ficha había que escribir el Console.WriteLine correspondiente cada vez, en
cualquier parte del programa que lo necesitara. Esta lección resuelve eso enseñándote a definir
métodos: bloques de código asociados a una clase que representan las acciones que sus
objetos pueden realizar. Al final de la lección, un objeto Libro sabrá prestarse, devolverse
y mostrar su propia ficha, sin que el código que lo usa tenga que conocer los detalles internos
de cómo lo hace.
Contenido
- Qué es un método y por qué vive dentro de la clase
- Sintaxis de un método: parámetros y tipo de retorno
- Métodos
void: acciones sin valor de retorno - Añadiendo métodos a
Libro:Prestar(),Devolver(),MostrarFicha() - Sobrecarga de métodos
- Paso de parámetros por valor
- Paso de parámetros por referencia:
refyout - Parámetros opcionales y argumentos nombrados
- Métodos de instancia frente a métodos
static
- Qué es un método y por qué vive dentro de la clase
Un método es una función definida dentro de una clase que describe una acción que sus
objetos pueden ejecutar. La diferencia con las funciones locales que ya usaste en el Módulo 2
(como PrestarLibro(string titulo), que recibía el título como parámetro) es que un método
vive dentro de la clase y, normalmente, opera directamente sobre las propiedades del propio
objeto, sin necesidad de que se las pasen como parámetros.
class Libro
{
public string Titulo { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public void Prestar()
{
Disponible = false;
}
}Fíjate en la diferencia de enfoque: en el Módulo 2, PrestarLibro(tituloLibros, disponibilidad, posicion) necesitaba que le pasaras el array de disponibilidad entero como parámetro. Ahora,
libro1.Prestar() no necesita ningún parámetro: el método ya sabe a qué objeto pertenece (a
libro1) y accede directamente a su propiedad Disponible. Esta capacidad de un método de
acceder a las propiedades del objeto sobre el que se invoca es la base de la programación
orientada a objetos.
- Sintaxis de un método: parámetros y tipo de retorno
La forma general de un método es:
- El modificador de acceso (
public,private...) controla desde dónde se puede llamar al método; se estudia en profundidad en la lección de Encapsulamiento. De momento, usaremospublicpara que sea accesible desde cualquier parte del programa. - El tipo de retorno indica qué tipo de dato devuelve el método al terminar (
int,string,bool...), ovoidsi no devuelve ningún valor. - Los parámetros, entre paréntesis, son los datos que el método necesita recibir para hacer su trabajo (pueden ser cero, uno o varios).
Este método devuelve un bool: true si el Autor del libro coincide con el parámetro
autorBuscado, false en caso contrario. La palabra clave return termina la ejecución del
método y entrega el valor indicado a quien lo llamó.
Libro libro1 = new Libro { Titulo = "Rayuela", Autor = "Julio Cortazar" };
bool esDeCortazar = libro1.TieneAutor("Julio Cortazar");
Console.WriteLine(esDeCortazar); // True
- Métodos
void: acciones sin valor de retorno
void: acciones sin valor de retornoCuando un método realiza una acción pero no necesita devolver ningún dato, su tipo de retorno
es void (literalmente, "vacío"). Ya usaste void en las funciones locales del Módulo 2; en
un método de clase se comporta igual:
public void MostrarFicha()
{
Console.WriteLine($"Titulo: {Titulo}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}Un método void puede usar return; (sin ningún valor detrás) para terminar antes de tiempo
si hace falta, pero no puede usar return valor;, ya que no tiene ningún tipo al que devolver
ese valor.
- Añadiendo métodos a
Libro: Prestar(), Devolver(), MostrarFicha()
Libro: Prestar(), Devolver(), MostrarFicha()Con lo visto hasta ahora, ya puedes completar la clase Libro con sus tres métodos
principales, que acompañarán al objeto durante el resto del módulo:
class Libro
{
public string Titulo { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public void Prestar()
{
if (Disponible)
{
Disponible = false;
Console.WriteLine($"'{Titulo}' ha sido prestado.");
}
else
{
Console.WriteLine($"'{Titulo}' no esta disponible para prestamo.");
}
}
public void Devolver()
{
Disponible = true;
Console.WriteLine($"'{Titulo}' ha sido devuelto.");
}
public void MostrarFicha()
{
Console.WriteLine($"Titulo: {Titulo}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}
}Y así se usan sobre un objeto concreto:
Libro libro1 = new Libro { Titulo = "Cien anos de soledad", Autor = "Gabriel Garcia Marquez", Isbn = "978-84-376-0494-7", Disponible = true };
libro1.MostrarFicha();
libro1.Prestar(); // 'Cien anos de soledad' ha sido prestado.
libro1.Prestar(); // 'Cien anos de soledad' no esta disponible para prestamo.
libro1.Devolver(); // 'Cien anos de soledad' ha sido devuelto.Observa la diferencia con el Módulo 2: libro1.Prestar() no necesita que le pases ni el
título, ni el array de disponibilidad, ni ninguna posición; el propio objeto ya conoce sus
datos y decide, según su estado interno (Disponible), qué hacer. Esta es la mejora concreta
que aporta la orientación a objetos frente a las variables sueltas del Módulo 2.
- Sobrecarga de métodos
La sobrecarga (overloading) consiste en definir varios métodos con el mismo nombre pero distinta lista de parámetros (distinto número de parámetros, o distinto tipo). C# decide automáticamente cuál de las versiones ejecutar según los argumentos con los que se le llame.
public void MostrarFicha()
{
Console.WriteLine($"Titulo: {Titulo}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}
public void MostrarFicha(bool incluirIsbn)
{
Console.WriteLine($"Titulo: {Titulo}");
Console.WriteLine($"Autor: {Autor}");
if (incluirIsbn)
{
Console.WriteLine($"ISBN: {Isbn}");
}
Console.WriteLine($"Disponible: {Disponible}");
}libro1.MostrarFicha(); // usa la version sin parametros
libro1.MostrarFicha(false); // usa la version con un bool, sin mostrar el ISBNC# distingue estas dos versiones únicamente por su firma (nombre + número y tipos de parámetros), no por el tipo de retorno ni por los nombres de los parámetros. Dos métodos con el mismo nombre y exactamente los mismos parámetros —aunque tuvieran tipos de retorno distintos— no serían una sobrecarga válida y el compilador lo rechazaría.
| Método | Parámetros | ¿Cuándo se usa? |
|---|---|---|
MostrarFicha() |
Ninguno | Ficha completa por defecto |
MostrarFicha(bool incluirIsbn) |
Un bool |
Se puede decidir si mostrar o no el ISBN |
- Paso de parámetros por valor
Por defecto, en C# los parámetros se pasan por valor: el método recibe una copia del
valor que se le pasa, no el original. Si el parámetro es de un tipo primitivo (int, bool,
double...), modificar el parámetro dentro del método no afecta a la variable original que se
pasó al llamarlo.
void Duplicar(int numero)
{
numero = numero * 2;
Console.WriteLine($"Dentro del metodo: {numero}");
}
int copiasDisponibles = 5;
Duplicar(copiasDisponibles);
Console.WriteLine($"Fuera del metodo: {copiasDisponibles}"); // sigue siendo 5Aunque dentro de Duplicar la variable local numero pasa a valer 10, la variable original
copiasDisponibles, fuera del método, no se ve afectada: numero era solo una copia.
Nota: esto es distinto de lo que viste en la lección anterior con objetos como
Libro. Un parámetro de tipoLibrotambién se pasa "por valor", pero lo que se copia es la referencia al objeto, no el objeto en sí; por eso, si un método recibe unLibrocomo parámetro y modifica una de sus propiedades (libro.Disponible = false;), ese cambio sí se refleja fuera del método, porque la copia de la referencia sigue apuntando al mismo objeto. Volveremos sobre esta diferencia entre tipos por valor y por referencia en la última lección del módulo.
- Paso de parámetros por referencia:
ref y out
ref y outCuando sí necesitas que un método modifique directamente la variable original (no una copia),
C# ofrece dos palabras clave: ref y out.
ref: modificar una variable ya inicializada
ref indica que el parámetro se pasa por referencia: el método recibe acceso directo a la
variable original, no a una copia, así que cualquier cambio dentro del método se refleja
fuera. La variable debe estar inicializada antes de la llamada, y hay que repetir ref tanto
al declarar el método como al llamarlo.
void DuplicarEjemplares(ref int cantidadEjemplares)
{
cantidadEjemplares = cantidadEjemplares * 2;
}
int ejemplaresRayuela = 3;
DuplicarEjemplares(ref ejemplaresRayuela);
Console.WriteLine(ejemplaresRayuela); // 6 -- esta vez SI cambioout: un método que "devuelve" varios valores
out se usa para que un método pueda entregar un valor adicional además de (o en lugar de) su
return, típicamente para indicar si una operación tuvo éxito junto con un mensaje o
resultado. A diferencia de ref, la variable pasada como out no necesita estar
inicializada antes de la llamada —el método está obligado a asignarle un valor antes de
terminar. Ya usaste este patrón, sin saberlo todavía, con int.TryParse(entrada, out int numero) en el Módulo 1.
bool TryPrestar(Libro libro, out string mensaje)
{
if (libro.Disponible)
{
libro.Disponible = false;
mensaje = $"'{libro.Titulo}' prestado correctamente.";
return true;
}
mensaje = $"'{libro.Titulo}' no esta disponible.";
return false;
}
Libro libro2 = new Libro { Titulo = "Ficciones", Disponible = true };
bool exito = TryPrestar(libro2, out string resultado);
Console.WriteLine(resultado); // 'Ficciones' prestado correctamente.
Console.WriteLine(exito); // Trueref |
out |
|
|---|---|---|
| La variable debe estar inicializada antes de llamar | Sí | No |
| El método está obligado a asignarle un valor | No | Sí |
| Uso típico | Modificar un valor existente | Devolver un resultado adicional (a menudo junto a un bool de éxito) |
- Parámetros opcionales y argumentos nombrados
Un parámetro opcional tiene un valor por defecto asignado en la propia definición del método; si quien llama al método no proporciona ese argumento, se usa el valor por defecto. Los parámetros opcionales deben ir siempre después de los parámetros obligatorios.
public void RegistrarEjemplar(string titulo, string autor, string estado = "Nuevo")
{
Console.WriteLine($"Registrado: {titulo} ({autor}) - Estado: {estado}");
}libro1.RegistrarEjemplar("Rayuela", "Julio Cortazar"); // usa "Nuevo" por defecto
libro1.RegistrarEjemplar("Rayuela", "Julio Cortazar", "Usado"); // sobreescribe el valor por defectoLos argumentos nombrados permiten indicar explícitamente a qué parámetro corresponde cada
valor, escribiendo nombreParametro: valor, en lugar de depender únicamente del orden. Esto es
especialmente útil cuando un método tiene varios parámetros opcionales y solo quieres
especificar alguno de ellos:
libro1.RegistrarEjemplar(titulo: "Ficciones", autor: "Jorge Luis Borges", estado: "Usado");
// Con argumentos nombrados, el orden deja de importar:
libro1.RegistrarEjemplar(autor: "Jorge Luis Borges", titulo: "Ficciones");En la segunda llamada, aunque autor se escribe antes que titulo, C# asigna cada valor al
parámetro correcto gracias al nombre explícito, y estado toma su valor por defecto
("Nuevo") al no indicarse.
- Métodos de instancia frente a métodos
static
staticTodos los métodos vistos hasta ahora son métodos de instancia: solo tiene sentido
llamarlos sobre un objeto concreto (libro1.Prestar()), porque operan sobre los datos de ese
objeto en particular. Un método static, en cambio, pertenece a la propia clase, no a
ningún objeto concreto, y se invoca escribiendo el nombre de la clase en lugar del de una
variable:
class Libro
{
public string Titulo { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public static bool EsIsbnValido(string isbn)
{
string isbnLimpio = isbn.Replace("-", "");
return isbnLimpio.Length == 13 && isbnLimpio.StartsWith("978") || isbnLimpio.StartsWith("979");
}
// Prestar(), Devolver(), MostrarFicha() se mantienen como en el apartado 4
}Fíjate en que EsIsbnValido se invoca como Libro.EsIsbnValido(...), usando el nombre de la
clase, no sobre un objeto (libro1.EsIsbnValido(...) sería un error de compilación). Esto
tiene sentido porque validar el formato de un ISBN no depende de ningún libro concreto: es una
utilidad general relacionada con el concepto Libro, pero que no necesita ningún objeto para
ejecutarse. Por esta misma razón, un método static no puede acceder directamente a
propiedades de instancia como Titulo o Disponible (no sabría de qué objeto hablar);
recibiría un error de compilación si lo intentara.
| Método de instancia | Método static |
|
|---|---|---|
| Se invoca sobre | Un objeto (libro1.Prestar()) |
La clase (Libro.EsIsbnValido(...)) |
Accede a propiedades del objeto (Titulo, Disponible...) |
Sí | No, directamente |
Ejemplo en Libro |
Prestar(), Devolver(), MostrarFicha() |
EsIsbnValido(string isbn) |
| Cuándo usarlo | La operación depende de los datos de un objeto concreto | La operación es una utilidad general, no ligada a ningún objeto |
Console.WriteLine y int.Parse, que ya has usado en módulos anteriores, son también métodos
static (de las clases Console e int respectivamente): por eso se llaman con el nombre de
la clase (Console, no una variable), y no sobre ningún objeto.
Errores Comunes y Consejos
- Olvidar
ref/outal llamar al método: si el método se declaró conrefuout, hay que repetir la palabra clave también en la llamada (DuplicarEjemplares(ref ejemplaresRayuela)), no solo en la definición; si no, el compilador da un error. - Usar una variable
outsin inicializarla y esperar leerla antes de la llamada: una variable declarada comoout string mensaje(inline, en la propia llamada) no tiene ningún valor útil hasta que el método termina de ejecutarse; no intentes leerla antes. - Confundir sobrecarga con parámetros opcionales: ambos permiten "llamar al método de varias formas", pero son mecanismos distintos: la sobrecarga define métodos completamente separados (con cuerpos potencialmente distintos), mientras que un parámetro opcional forma parte de un único método con un valor por defecto.
- Intentar acceder a
TitulooDisponibledesde un métodostatic: un métodostaticno está asociado a ningún objeto, así que no puede usar directamente propiedades de instancia; si necesita datos de un libro concreto, ese libro debe llegarle como parámetro. - Abusar de
ref/out: son útiles en casos concretos (comoTryParse/TryPrestar), pero un método que devuelve su resultado conreturnsuele ser más claro que uno que lo hace mediante parámetrosref/out; resérvalos para cuando de verdad aporten algo, no como costumbre.
Ejercicios
-
Añade a la clase
Libro(con las propiedadesTitulo,Autor,Isbn,Disponible) un método de instanciabool EsDelAutor(string autorBuscado)que devuelvatruesi elAutordel libro coincide exactamente conautorBuscado, yfalseen caso contrario. Pruébalo con un objetoLibroy dos búsquedas distintas. -
Escribe un método
staticbool EsIsbnValido(string isbn)dentro de la claseLibroque devuelvatruesi, tras quitar los guiones conReplace("-", ""), la cadena resultante tiene exactamente 13 caracteres, yfalseen caso contrario. Llámalo conLibro.EsIsbnValido(...)usando dos ISBN de ejemplo (uno válido y otro no). -
Escribe un método
void RegistrarPrestamo(string nombreSocio, int diasPrestamo = 15)que muestre por consola un mensaje con el nombre del socio y los días de préstamo. Llámalo tres veces: una sin indicardiasPrestamo(debe usar 15 por defecto), otra indicando 30 días por posición, y otra usando argumentos nombrados en orden invertido.
Soluciones
class Libro
{
public string Titulo { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public bool EsDelAutor(string autorBuscado)
{
return Autor == autorBuscado;
}
}
Libro libro1 = new Libro { Titulo = "Rayuela", Autor = "Julio Cortazar" };
Console.WriteLine(libro1.EsDelAutor("Julio Cortazar")); // True
Console.WriteLine(libro1.EsDelAutor("Jorge Luis Borges")); // False
public static bool EsIsbnValido(string isbn)
{
string isbnLimpio = isbn.Replace("-", "");
return isbnLimpio.Length == 13;
}
Console.WriteLine(Libro.EsIsbnValido("978-84-376-0494-7")); // True (13 caracteres sin guiones)
Console.WriteLine(Libro.EsIsbnValido("123")); // False
void RegistrarPrestamo(string nombreSocio, int diasPrestamo = 15)
{
Console.WriteLine($"{nombreSocio}: prestamo de {diasPrestamo} dias.");
}
RegistrarPrestamo("Ana Martinez"); // usa 15 dias por defecto
RegistrarPrestamo("Luis Gomez", 30); // 30 dias por posicion
RegistrarPrestamo(diasPrestamo: 7, nombreSocio: "Eva Ruiz"); // argumentos nombrados, orden invertido
Las tres llamadas son válidas: la primera usa el valor por defecto de diasPrestamo, la
segunda lo indica por posición, y la tercera usa argumentos nombrados, lo que permite
escribirlos en cualquier orden.
Conclusión
En esta lección, Libro ha pasado de ser un simple contenedor de datos a un objeto con
comportamiento propio: sabe prestarse (Prestar()), devolverse (Devolver()) y mostrar su
ficha (MostrarFicha()), y has aprendido las herramientas fundamentales para definir métodos
en C#: parámetros y retorno, void, sobrecarga, paso por valor frente a ref/out,
parámetros opcionales y nombrados, y la diferencia entre métodos de instancia y métodos
static.
Todavía queda una pregunta pendiente: ¿cómo se asegura que un objeto Libro nace siempre con
sus propiedades bien inicializadas (por ejemplo, Disponible = true al crear un libro nuevo),
en lugar de confiar en que quien lo crea recuerde asignar cada propiedad a mano? La respuesta
son los constructores, que verás en la próxima lección junto con la nueva clase Socio:
el segundo pilar del modelo de dominio de BiblioTech.
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Arrays y Cadenas de Texto
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- Structs y Records: Tipos por Valor y por Referencia
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Pattern Matching y Características Modernas de C#
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
- Entrada/Salida de Archivos
- Serialización
- Conectividad con Bases de Datos
- Entity Framework
- Trabajo con JSON y Consumo de APIs REST
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
- Formularios de Windows
- WPF (Windows Presentation Foundation)
- ASP.NET Core
- Blazor
- Xamarin y .NET MAUI
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Inyección de Dependencias e Inversión de Control
- Pruebas Unitarias
- Revisión y Refactorización de Código
