Ir al contenido principal

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.