Propiedades y métodos
Introducción
Las propiedades y métodos son los mecanismos principales que nos permiten interactuar con los objetos en C#. Mientras que los métodos definen las acciones que puede realizar un objeto, las propiedades proporcionan una forma elegante y controlada de acceder y modificar los datos del objeto. Las propiedades representan una evolución natural de los campos públicos, ofreciendo mayor flexibilidad y control sin sacrificar la simplicidad de uso.
En el artículo anterior aprendimos a crear clases básicas con campos privados y métodos públicos. Ahora profundizaremos en cómo las propiedades nos permiten encapsular el acceso a los datos de manera más sofisticada, y exploraremos las diferentes formas de implementar métodos que aporten funcionalidad rica a nuestras clases.
¿Qué son las propiedades?
Una propiedad es un miembro de clase que proporciona un mecanismo flexible para leer, escribir o calcular el valor de un campo privado. Las propiedades actúan como campos públicos desde el punto de vista del código cliente, pero internamente pueden ejecutar lógica personalizada cuando se accede a ellas o se modifican.
Ventajas de las propiedades sobre los campos públicos
Aspecto | Campo público | Propiedad |
---|---|---|
Validación | No permite validación | Puede validar datos antes de asignar |
Cálculo | Valor estático | Puede calcular valores dinámicamente |
Notificación | Sin notificación de cambios | Puede notificar cuando cambia el valor |
Compatibilidad | Cambios rompen la interfaz | Cambios mantienen la compatibilidad |
Depuración | Difícil de rastrear accesos | Permite puntos de interrupción en get/set |
Sintaxis básica de propiedades
public class Ejemplo
{
// Campo privado
private int edad;
// Propiedad con get y set explícitos
public int Edad
{
get { return edad; }
set { edad = value; }
}
// Propiedad automática (más común)
public string Nombre { get; set; }
// Propiedad de solo lectura
public DateTime FechaCreacion { get; }
// Propiedad calculada
public string NombreCompleto
{
get { return $"{Nombre} - {Edad} años"; }
}
}
Propiedades automáticas
Las propiedades automáticas son la forma más común de implementar propiedades simples en C#. El compilador genera automáticamente el campo privado de respaldo:
public class Producto
{
// Propiedades automáticas básicas
public string Nombre { get; set; }
public decimal Precio { get; set; }
public string Categoria { get; set; }
// Propiedad automática con valor inicial
public DateTime FechaCreacion { get; set; } = DateTime.Now;
// Propiedad automática de solo lectura
public string Codigo { get; }
// Constructor que inicializa propiedades
public Producto(string nombre, decimal precio, string categoria)
{
Nombre = nombre;
Precio = precio;
Categoria = categoria;
Codigo = GenerarCodigo(); // Solo se puede asignar en el constructor
}
private string GenerarCodigo()
{
return $"{Categoria.Substring(0, 3).ToUpper()}-{DateTime.Now.Ticks % 10000}";
}
public void MostrarInformacion()
{
Console.WriteLine($"Código: {Codigo}");
Console.WriteLine($"Nombre: {Nombre}");
Console.WriteLine($"Precio: {Precio:C}");
Console.WriteLine($"Categoría: {Categoria}");
Console.WriteLine($"Creado: {FechaCreacion}");
}
}
Propiedades con lógica personalizada
Cuando necesitamos validación o cálculos, implementamos propiedades con bloques get
y set
personalizados:
public class CuentaBancaria
{
private decimal saldo;
private string titular;
private DateTime fechaApertura;
public CuentaBancaria(string titular, decimal saldoInicial)
{
Titular = titular;
Saldo = saldoInicial;
fechaApertura = DateTime.Now;
}
// Propiedad con validación en el set
public string Titular
{
get { return titular; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("El titular no puede estar vacío");
titular = value.Trim().ToUpperInvariant();
}
}
// Propiedad con validación de saldo
public decimal Saldo
{
get { return saldo; }
private set // Solo la clase puede modificar el saldo
{
if (value < 0)
throw new ArgumentException("El saldo no puede ser negativo");
saldo = value;
}
}
// Propiedad calculada basada en el tiempo transcurrido
public int DiasAbierta
{
get { return (DateTime.Now - fechaApertura).Days; }
}
// Propiedad calculada para determinar tipo de cuenta
public string TipoCuenta
{
get
{
if (saldo >= 10000) return "Premium";
if (saldo >= 1000) return "Estándar";
return "Básica";
}
}
// Métodos para modificar el saldo de forma controlada
public void Depositar(decimal cantidad)
{
if (cantidad <= 0)
throw new ArgumentException("La cantidad debe ser positiva");
Saldo += cantidad; // Usa la propiedad para mantener validación
Console.WriteLine($"Depósito de {cantidad:C} realizado. Saldo actual: {Saldo:C}");
}
public bool Retirar(decimal cantidad)
{
if (cantidad <= 0)
throw new ArgumentException("La cantidad debe ser positiva");
if (cantidad > Saldo)
{
Console.WriteLine("Fondos insuficientes");
return false;
}
Saldo -= cantidad;
Console.WriteLine($"Retiro de {cantidad:C} realizado. Saldo actual: {Saldo:C}");
return true;
}
}
Tipos de métodos
Los métodos en C# pueden clasificarse según diferentes criterios:
Por su valor de retorno
Tipo | Descripción | Ejemplo |
---|---|---|
Void | No devuelven valor | public void MostrarMensaje() |
Con retorno | Devuelven un valor específico | public int CalcularEdad() |
Bool | Devuelven verdadero o falso | public bool EsValido() |
Por sus parámetros
Tipo | Descripción | Sintaxis |
---|---|---|
Sin parámetros | No reciben argumentos | public void Metodo() |
Con parámetros | Reciben valores | public void Metodo(int x, string y) |
Parámetros opcionales | Tienen valores por defecto | public void Metodo(int x = 10) |
Parámetros por referencia | Modifican el valor original | public void Metodo(ref int x) |
Ejemplo completo: Clase Empleado
Veamos una clase más compleja que combina diferentes tipos de propiedades y métodos:
public class Empleado
{
private decimal salarioBase;
private DateTime fechaIngreso;
// Propiedades automáticas
public string Nombre { get; set; }
public string Apellido { get; set; }
public string Departamento { get; set; }
public string Email { get; private set; }
// Propiedad con validación
public decimal SalarioBase
{
get { return salarioBase; }
set
{
if (value <= 0)
throw new ArgumentException("El salario debe ser mayor que cero");
salarioBase = value;
}
}
// Propiedad calculada de solo lectura
public string NombreCompleto
{
get { return $"{Nombre} {Apellido}"; }
}
// Propiedad calculada para antigüedad
public int AntiguedadEnAnios
{
get
{
var antiguedad = DateTime.Now - fechaIngreso;
return (int)(antiguedad.Days / 365.25); // Considera años bisiestos
}
}
// Propiedad calculada para bono por antigüedad
public decimal BonoAntiguedad
{
get
{
int anios = AntiguedadEnAnios;
if (anios >= 10) return salarioBase * 0.20m;
if (anios >= 5) return salarioBase * 0.10m;
if (anios >= 2) return salarioBase * 0.05m;
return 0;
}
}
// Constructor
public Empleado(string nombre, string apellido, string departamento, decimal salarioBase)
{
Nombre = nombre;
Apellido = apellido;
Departamento = departamento;
SalarioBase = salarioBase;
fechaIngreso = DateTime.Now;
Email = GenerarEmail();
}
// Método privado para generar email
private string GenerarEmail()
{
string nombreLimpio = Nombre.ToLower().Replace(" ", "");
string apellidoLimpio = Apellido.ToLower().Replace(" ", "");
return $"{nombreLimpio}.{apellidoLimpio}@empresa.com";
}
// Método para calcular salario total
public decimal CalcularSalarioTotal()
{
return SalarioBase + BonoAntiguedad;
}
// Método para aumentar salario
public void AumentarSalario(decimal porcentaje)
{
if (porcentaje <= 0)
throw new ArgumentException("El porcentaje debe ser positivo");
decimal salarioAnterior = SalarioBase;
SalarioBase += SalarioBase * (porcentaje / 100);
Console.WriteLine($"Salario aumentado de {salarioAnterior:C} a {SalarioBase:C} " +
$"({porcentaje}% de aumento)");
}
// Método para cambiar departamento
public void CambiarDepartamento(string nuevoDepartamento)
{
if (string.IsNullOrWhiteSpace(nuevoDepartamento))
throw new ArgumentException("El departamento no puede estar vacío");
string departamentoAnterior = Departamento;
Departamento = nuevoDepartamento;
Console.WriteLine($"{NombreCompleto} ha sido transferido de {departamentoAnterior} " +
$"a {Departamento}");
}
// Método para mostrar información completa
public void MostrarInformacionCompleta()
{
Console.WriteLine("=== INFORMACIÓN DEL EMPLEADO ===");
Console.WriteLine($"Nombre completo: {NombreCompleto}");
Console.WriteLine($"Email: {Email}");
Console.WriteLine($"Departamento: {Departamento}");
Console.WriteLine($"Fecha de ingreso: {fechaIngreso.ToShortDateString()}");
Console.WriteLine($"Antigüedad: {AntiguedadEnAnios} años");
Console.WriteLine($"Salario base: {SalarioBase:C}");
Console.WriteLine($"Bono antigüedad: {BonoAntiguedad:C}");
Console.WriteLine($"Salario total: {CalcularSalarioTotal():C}");
}
// Método con parámetros opcionales
public void EnviarNotificacion(string mensaje, bool esUrgente = false, string destinatario = null)
{
destinatario = destinatario ?? Email; // Si no se especifica, usa el email del empleado
string prioridad = esUrgente ? "[URGENTE] " : "";
Console.WriteLine($"Enviando notificación a {destinatario}: {prioridad}{mensaje}");
}
}
Usando la clase Empleado
class Program
{
static void Main()
{
// Crear empleados
Empleado empleado1 = new Empleado("Ana", "García López", "Desarrollo", 35000);
Empleado empleado2 = new Empleado("Carlos", "Martínez", "Ventas", 32000);
Console.WriteLine("=== INFORMACIÓN INICIAL ===");
empleado1.MostrarInformacionCompleta();
Console.WriteLine();
empleado2.MostrarInformacionCompleta();
Console.WriteLine("\n=== OPERACIONES ===");
// Aumentar salario
empleado1.AumentarSalario(15);
// Cambiar departamento
empleado2.CambiarDepartamento("Marketing");
// Enviar notificaciones
empleado1.EnviarNotificacion("Reunión de equipo mañana a las 10:00");
empleado2.EnviarNotificacion("Informe mensual pendiente", true);
empleado2.EnviarNotificacion("Bienvenido al nuevo departamento",
false,
"jefe.marketing@empresa.com");
Console.WriteLine("\n=== ESTADO FINAL ===");
Console.WriteLine($"Empleado 1 - Salario total: {empleado1.CalcularSalarioTotal():C}");
Console.WriteLine($"Empleado 2 - Nuevo departamento: {empleado2.Departamento}");
}
}
Propiedades de solo lectura y escritura
C# permite crear propiedades con acceso limitado:
public class ConfiguracionSistema
{
private string claveLicencia;
// Solo lectura desde fuera de la clase
public DateTime FechaCreacion { get; private set; }
public string Version { get; } = "1.0.0"; // Solo asignable en declaración o constructor
// Solo escritura (poco común, pero posible)
public string ClaveLicencia
{
private get { return claveLicencia; }
set
{
if (ValidarLicencia(value))
claveLicencia = value;
else
throw new ArgumentException("Clave de licencia inválida");
}
}
// Propiedad calculada compleja
public string EstadoSistema
{
get
{
if (string.IsNullOrEmpty(claveLicencia)) return "Sin licencia";
if ((DateTime.Now - FechaCreacion).Days > 365) return "Licencia expirada";
return "Sistema activo";
}
}
public ConfiguracionSistema()
{
FechaCreacion = DateTime.Now;
}
private bool ValidarLicencia(string clave)
{
// Lógica de validación simplificada
return !string.IsNullOrEmpty(clave) && clave.Length == 16;
}
public bool EsLicenciaValida()
{
return !string.IsNullOrEmpty(claveLicencia) &&
(DateTime.Now - FechaCreacion).Days <= 365;
}
}
Métodos con diferentes tipos de parámetros
public class CalculadoraAvanzada
{
// Método con parámetros normales
public double Sumar(double a, double b)
{
return a + b;
}
// Método con parámetros opcionales
public double CalcularPotencia(double baseNum, double exponente = 2)
{
return Math.Pow(baseNum, exponente);
}
// Método con parámetros por referencia
public void IntercambiarValores(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
// Método con parámetros de salida
public bool DividirConResto(int dividendo, int divisor, out int cociente, out int resto)
{
if (divisor == 0)
{
cociente = 0;
resto = 0;
return false;
}
cociente = dividendo / divisor;
resto = dividendo % divisor;
return true;
}
// Método con array de parámetros (params)
public double Promedio(params double[] numeros)
{
if (numeros.Length == 0) return 0;
double suma = 0;
foreach (double numero in numeros)
{
suma += numero;
}
return suma / numeros.Length;
}
}
Ejemplo de uso de estos métodos:
class Program
{
static void Main()
{
CalculadoraAvanzada calc = new CalculadoraAvanzada();
// Método normal
Console.WriteLine($"Suma: {calc.Sumar(10, 5)}");
// Método con parámetros opcionales
Console.WriteLine($"Cuadrado de 4: {calc.CalcularPotencia(4)}");
Console.WriteLine($"4 elevado a 3: {calc.CalcularPotencia(4, 3)}");
// Método con ref
int x = 10, y = 20;
Console.WriteLine($"Antes: x={x}, y={y}");
calc.IntercambiarValores(ref x, ref y);
Console.WriteLine($"Después: x={x}, y={y}");
// Método con out
if (calc.DividirConResto(17, 5, out int cociente, out int resto))
{
Console.WriteLine($"17 ÷ 5 = {cociente} resto {resto}");
}
// Método con params
Console.WriteLine($"Promedio: {calc.Promedio(1, 2, 3, 4, 5)}");
Console.WriteLine($"Promedio: {calc.Promedio(10.5, 20.3, 15.7)}");
}
}
Resumen
Las propiedades y métodos son elementos fundamentales que definen la interfaz y el comportamiento de nuestras clases. Las propiedades nos permiten encapsular el acceso a los datos con validación y lógica personalizada, mientras mantienen una sintaxis simple para el código cliente. Los métodos definen las acciones que pueden realizar los objetos, con diferentes tipos de parámetros que ofrecen flexibilidad en su uso. Hemos visto desde propiedades automáticas simples hasta propiedades calculadas complejas, y desde métodos básicos hasta aquellos con parámetros opcionales, por referencia y arrays de parámetros. Esta base sólida nos prepara para explorar conceptos más avanzados como constructores, encapsulación y herencia en los próximos artículos.