CSharp
Tutorial CSharp: Propiedades y encapsulación
Aprende los modificadores de acceso y propiedades en C# para controlar la encapsulación y proteger datos en tus clases con ejemplos prácticos.
Aprende CSharp y certifícateModificadores de acceso
Los modificadores de acceso son una parte fundamental de la encapsulación en C#, ya que permiten controlar la visibilidad y accesibilidad de los miembros de una clase (campos, métodos, propiedades) desde otras partes del código. Estos modificadores son la primera línea de defensa para proteger los datos internos de una clase.
En C#, existen cuatro modificadores de acceso principales que puedes utilizar para controlar quién puede acceder a los miembros de tus clases:
- public: Acceso sin restricciones desde cualquier parte del código
- private: Acceso limitado solo a la clase que contiene el miembro
- protected: Acceso limitado a la clase que contiene el miembro y a sus clases derivadas
- internal: Acceso limitado al ensamblado (proyecto) actual
Veamos cómo funcionan estos modificadores con ejemplos prácticos.
Modificador public
Cuando declaras un miembro como public, este puede ser accedido desde cualquier parte del código, tanto dentro como fuera de la clase. Es el nivel de acceso menos restrictivo.
public class Persona
{
// Campo público (no recomendado)
public string nombre;
// Método público
public void Saludar()
{
Console.WriteLine($"Hola, soy {nombre}");
}
}
// Uso desde otra clase
class Program
{
static void Main()
{
Persona persona = new Persona();
persona.nombre = "Ana"; // Acceso directo al campo público
persona.Saludar(); // Acceso al método público
}
}
Aunque el código anterior funciona, exponer campos públicamente como nombre
no es una buena práctica, ya que cualquier código podría modificar ese valor directamente, incluso asignando valores inválidos.
Modificador private
El modificador private es el más restrictivo y solo permite el acceso desde dentro de la misma clase. Es ideal para ocultar los detalles internos de implementación.
public class CuentaBancaria
{
// Campo privado
private decimal saldo;
// Método público que usa el campo privado
public void Depositar(decimal cantidad)
{
if (cantidad > 0)
{
saldo += cantidad;
Console.WriteLine($"Depósito de {cantidad:C} realizado. Nuevo saldo: {saldo:C}");
}
else
{
Console.WriteLine("La cantidad a depositar debe ser mayor que cero.");
}
}
}
// Uso desde otra clase
class Program
{
static void Main()
{
CuentaBancaria cuenta = new CuentaBancaria();
cuenta.Depositar(100);
// Esto generaría un error de compilación:
// cuenta.saldo = -5000; // Error: 'saldo' es inaccesible debido a su nivel de protección
}
}
En este ejemplo, el campo saldo
está protegido contra modificaciones directas desde fuera de la clase. Solo se puede modificar a través del método Depositar
, que incluye validaciones para garantizar que solo se acepten cantidades positivas.
Modificador protected
El modificador protected permite el acceso desde la clase que contiene el miembro y desde cualquier clase que herede de ella. Es útil cuando quieres que las clases derivadas puedan acceder a ciertos miembros sin exponerlos públicamente.
public class Vehiculo
{
// Campo protegido
protected int velocidad;
public void Acelerar(int incremento)
{
if (incremento > 0)
{
velocidad += incremento;
Console.WriteLine($"Acelerando a {velocidad} km/h");
}
}
}
// Clase derivada
public class Coche : Vehiculo
{
public void MostrarVelocidadActual()
{
// Puede acceder al campo protegido de la clase base
Console.WriteLine($"Velocidad actual del coche: {velocidad} km/h");
}
public void AplicarFrenoEmergencia()
{
// Puede modificar el campo protegido
velocidad = 0;
Console.WriteLine("¡Freno de emergencia aplicado!");
}
}
En este ejemplo, la clase Coche
puede acceder y modificar el campo velocidad
heredado de Vehiculo
porque está declarado como protected
.
Modificador internal
El modificador internal limita el acceso a los miembros dentro del mismo ensamblado (proyecto). Es útil cuando quieres compartir funcionalidad entre clases del mismo proyecto sin exponerla a proyectos externos.
// En un archivo del proyecto
internal class ConfiguracionInterna
{
internal static string ObtenerRutaArchivos()
{
return @"C:\Datos\Aplicacion";
}
}
// En otro archivo del mismo proyecto
public class GestorArchivos
{
public void GuardarArchivo(string nombre, byte[] datos)
{
// Puede acceder a la clase y método internal
string ruta = ConfiguracionInterna.ObtenerRutaArchivos();
string rutaCompleta = Path.Combine(ruta, nombre);
// Código para guardar el archivo...
Console.WriteLine($"Archivo guardado en: {rutaCompleta}");
}
}
Combinación de modificadores
C# también permite combinar internal
con protected
para crear el modificador protected internal
, que permite el acceso desde el mismo ensamblado o desde clases derivadas en otros ensamblados.
public class ComponenteBase
{
// Accesible desde el mismo ensamblado o desde clases derivadas
protected internal void InicializarComponente()
{
Console.WriteLine("Componente inicializado");
}
}
Beneficios de usar modificadores de acceso adecuados
Utilizar los modificadores de acceso correctamente proporciona varios beneficios:
- Encapsulación: Oculta los detalles de implementación y expone solo lo necesario.
- Seguridad: Previene modificaciones no autorizadas de los datos.
- Mantenibilidad: Facilita los cambios internos sin afectar al código que usa la clase.
- Robustez: Permite implementar validaciones antes de modificar el estado interno.
Ejemplo práctico: Encapsulación con modificadores de acceso
Veamos un ejemplo completo que muestra cómo los modificadores de acceso contribuyen a la encapsulación:
public class Empleado
{
// Campos privados
private string nombre;
private int edad;
private decimal salario;
// Constructor público
public Empleado(string nombre, int edad, decimal salarioInicial)
{
this.nombre = nombre;
// Validación de la edad
if (edad >= 18 && edad <= 65)
this.edad = edad;
else
throw new ArgumentException("La edad debe estar entre 18 y 65 años");
// Validación del salario
if (salarioInicial >= 0)
this.salario = salarioInicial;
else
throw new ArgumentException("El salario no puede ser negativo");
}
// Método público
public void AumentarSalario(decimal porcentaje)
{
if (porcentaje > 0)
{
decimal aumento = salario * porcentaje / 100;
salario += aumento;
Console.WriteLine($"{nombre} recibió un aumento del {porcentaje}%. Nuevo salario: {salario:C}");
}
else
{
Console.WriteLine("El porcentaje de aumento debe ser positivo");
}
}
// Método público que muestra información
public void MostrarInformacion()
{
Console.WriteLine($"Empleado: {nombre}, Edad: {edad}, Salario: {salario:C}");
}
// Método privado (solo para uso interno)
private bool EsElegibleParaBono()
{
// Lógica interna que no necesita ser expuesta
return edad > 30 && salario < 50000;
}
// Método público que usa el método privado
public void ProcesarBonoAnual()
{
if (EsElegibleParaBono())
{
decimal bono = salario * 0.1m;
salario += bono;
Console.WriteLine($"{nombre} recibió un bono anual de {bono:C}");
}
else
{
Console.WriteLine($"{nombre} no es elegible para bono anual");
}
}
}
En este ejemplo:
- Los campos
nombre
,edad
ysalario
son privados, lo que impide su modificación directa desde fuera de la clase. - El constructor valida los datos antes de asignarlos a los campos privados.
- El método
AumentarSalario
proporciona una forma controlada de modificar el salario, incluyendo validaciones. - El método
EsElegibleParaBono
es privado porque contiene lógica interna que no necesita ser expuesta. - Los métodos
MostrarInformacion
yProcesarBonoAnual
son públicos porque representan operaciones que los usuarios de la clase necesitan realizar.
Así es como se utilizaría esta clase:
class Program
{
static void Main()
{
try
{
Empleado empleado = new Empleado("Carlos Gómez", 35, 45000);
empleado.MostrarInformacion();
empleado.AumentarSalario(5);
empleado.ProcesarBonoAnual();
// Esto no sería posible debido a los modificadores de acceso:
// empleado.salario = -10000; // Error: 'salario' es inaccesible
// empleado.EsElegibleParaBono(); // Error: 'EsElegibleParaBono' es inaccesible
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Este enfoque de encapsulación mediante modificadores de acceso garantiza que los datos del empleado se mantengan en un estado válido y que solo puedan ser modificados a través de métodos controlados que implementan la lógica de negocio necesaria.
Propiedades getter/setter
Las propiedades en C# son una característica fundamental que mejora la encapsulación al proporcionar una interfaz pública para acceder y modificar los campos privados de una clase. A diferencia de los campos públicos, las propiedades permiten implementar lógica adicional como validaciones, cálculos o notificaciones cuando se lee o modifica un valor.
Una propiedad completa en C# consta de dos partes principales:
- Un getter (accesador): método que permite leer el valor
- Un setter (mutador): método que permite modificar el valor
Estructura básica de una propiedad
La sintaxis básica de una propiedad con getter y setter es la siguiente:
private tipo campoPrivado;
public tipo NombrePropiedad
{
get { return campoPrivado; }
set { campoPrivado = value; }
}
Donde:
tipo
es el tipo de dato de la propiedadcampoPrivado
es el campo privado que almacena el valor realNombrePropiedad
es el nombre de la propiedad (por convención, en PascalCase)value
es una palabra clave especial que representa el valor que se está asignando
Ejemplo práctico de propiedades
Veamos un ejemplo sencillo de una clase Producto
que utiliza propiedades para encapsular sus datos:
public class Producto
{
// Campos privados
private string nombre;
private decimal precio;
private int stock;
// Propiedad para el nombre
public string Nombre
{
get { return nombre; }
set { nombre = value; }
}
// Propiedad para el precio con validación
public decimal Precio
{
get { return precio; }
set
{
if (value >= 0)
precio = value;
else
throw new ArgumentException("El precio no puede ser negativo");
}
}
// Propiedad para el stock con validación
public int Stock
{
get { return stock; }
set
{
if (value >= 0)
stock = value;
else
throw new ArgumentException("El stock no puede ser negativo");
}
}
}
Uso de propiedades vs campos públicos
Para entender mejor por qué las propiedades son preferibles a los campos públicos, comparemos dos enfoques:
Enfoque 1: Usando campos públicos (no recomendado)
public class ProductoMalo
{
public string Nombre; // Campo público
public decimal Precio; // Campo público
public int Stock; // Campo público
}
// Uso:
ProductoMalo producto = new ProductoMalo();
producto.Precio = -50; // ¡Permitido pero incorrecto!
Enfoque 2: Usando propiedades (recomendado)
public class ProductoBueno
{
private string nombre;
private decimal precio;
private int stock;
public string Nombre
{
get { return nombre; }
set { nombre = value; }
}
public decimal Precio
{
get { return precio; }
set
{
if (value >= 0)
precio = value;
else
throw new ArgumentException("El precio no puede ser negativo");
}
}
public int Stock
{
get { return stock; }
set
{
if (value >= 0)
stock = value;
else
throw new ArgumentException("El stock no puede ser negativo");
}
}
}
// Uso:
ProductoBueno producto = new ProductoBueno();
try
{
producto.Precio = -50; // Lanza excepción
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message); // "El precio no puede ser negativo"
}
Ventajas de usar propiedades
Las propiedades ofrecen varias ventajas importantes sobre los campos públicos:
- Validación de datos: Puedes verificar que los valores asignados cumplan con ciertas reglas.
- Cálculos derivados: Puedes calcular valores sobre la marcha en lugar de almacenarlos.
- Notificaciones: Puedes ejecutar código adicional cuando un valor cambia.
- Depuración: Puedes establecer puntos de interrupción en getters/setters para rastrear cambios.
- Compatibilidad con interfaces: Las interfaces pueden definir propiedades pero no campos.
- Flexibilidad: Puedes comenzar con una propiedad simple y agregar lógica más tarde sin cambiar la interfaz pública.
Propiedades de solo lectura y solo escritura
Puedes crear propiedades que solo permitan leer o escribir valores:
public class Usuario
{
private string nombre;
private DateTime fechaRegistro;
// Propiedad de lectura y escritura
public string Nombre
{
get { return nombre; }
set { nombre = value; }
}
// Propiedad de solo lectura (no tiene setter)
public DateTime FechaRegistro
{
get { return fechaRegistro; }
}
// Propiedad de solo escritura (no tiene getter)
public string Contraseña
{
set { GuardarContraseñaEncriptada(value); }
}
private void GuardarContraseñaEncriptada(string contraseña)
{
// Código para encriptar y guardar la contraseña
Console.WriteLine("Contraseña guardada de forma segura");
}
// Constructor
public Usuario()
{
fechaRegistro = DateTime.Now;
}
}
Propiedades calculadas
Las propiedades también pueden calcular valores dinámicamente sin necesidad de almacenarlos:
public class Rectangulo
{
private double ancho;
private double alto;
public double Ancho
{
get { return ancho; }
set
{
if (value > 0)
ancho = value;
else
throw new ArgumentException("El ancho debe ser positivo");
}
}
public double Alto
{
get { return alto; }
set
{
if (value > 0)
alto = value;
else
throw new ArgumentException("El alto debe ser positivo");
}
}
// Propiedad calculada (no tiene campo de respaldo)
public double Area
{
get { return ancho * alto; }
}
// Propiedad calculada
public double Perimetro
{
get { return 2 * (ancho + alto); }
}
}
En este ejemplo, Area
y Perimetro
son propiedades calculadas que no almacenan datos, sino que calculan un valor basado en otros campos.
Propiedades con diferentes niveles de acceso
Puedes definir diferentes niveles de acceso para los getters y setters:
public class Empleado
{
private string nombre;
private decimal salario;
// Propiedad con getter público y setter privado
public string Nombre
{
get { return nombre; }
private set { nombre = value; }
}
// Propiedad con getter público y setter protegido
public decimal Salario
{
get { return salario; }
protected set
{
if (value >= 0)
salario = value;
else
throw new ArgumentException("El salario no puede ser negativo");
}
}
// Constructor
public Empleado(string nombre, decimal salarioInicial)
{
this.Nombre = nombre; // Usa el setter privado
this.Salario = salarioInicial; // Usa el setter protegido
}
}
// Clase derivada
public class Gerente : Empleado
{
public Gerente(string nombre, decimal salarioInicial)
: base(nombre, salarioInicial)
{
}
public void AumentarSalario(decimal porcentaje)
{
if (porcentaje > 0)
{
decimal nuevoSalario = Salario * (1 + porcentaje / 100);
Salario = nuevoSalario; // Puede usar el setter protegido
}
}
}
En este ejemplo:
Nombre
tiene un getter público pero un setter privado, lo que significa que solo se puede establecer dentro de la claseEmpleado
.Salario
tiene un getter público pero un setter protegido, lo que permite que las clases derivadas comoGerente
modifiquen el salario.
Ejemplo completo: Sistema de gestión de biblioteca
Veamos un ejemplo más completo que muestra cómo las propiedades pueden mejorar la encapsulación en una aplicación real:
public class Libro
{
// Campos privados
private string titulo;
private string autor;
private string isbn;
private int paginas;
private bool prestado;
private DateTime? fechaPrestamo;
// Propiedades
public string Titulo
{
get { return titulo; }
set
{
if (!string.IsNullOrWhiteSpace(value))
titulo = value;
else
throw new ArgumentException("El título no puede estar vacío");
}
}
public string Autor
{
get { return autor; }
set
{
if (!string.IsNullOrWhiteSpace(value))
autor = value;
else
throw new ArgumentException("El autor no puede estar vacío");
}
}
public string ISBN
{
get { return isbn; }
set
{
if (!string.IsNullOrWhiteSpace(value) && value.Length == 13)
isbn = value;
else
throw new ArgumentException("El ISBN debe tener 13 caracteres");
}
}
public int Paginas
{
get { return paginas; }
set
{
if (value > 0)
paginas = value;
else
throw new ArgumentException("El número de páginas debe ser positivo");
}
}
// Propiedad de solo lectura
public bool EstaPrestado
{
get { return prestado; }
}
// Propiedad de solo lectura
public DateTime? FechaPrestamo
{
get { return fechaPrestamo; }
}
// Propiedad calculada
public bool EstaDisponible
{
get { return !prestado; }
}
// Constructor
public Libro(string titulo, string autor, string isbn, int paginas)
{
Titulo = titulo; // Usa la propiedad, no el campo
Autor = autor; // Usa la propiedad, no el campo
ISBN = isbn; // Usa la propiedad, no el campo
Paginas = paginas; // Usa la propiedad, no el campo
prestado = false;
fechaPrestamo = null;
}
// Métodos que modifican el estado
public void Prestar()
{
if (!prestado)
{
prestado = true;
fechaPrestamo = DateTime.Now;
Console.WriteLine($"Libro '{titulo}' prestado con éxito.");
}
else
{
Console.WriteLine($"El libro '{titulo}' ya está prestado.");
}
}
public void Devolver()
{
if (prestado)
{
prestado = false;
fechaPrestamo = null;
Console.WriteLine($"Libro '{titulo}' devuelto con éxito.");
}
else
{
Console.WriteLine($"El libro '{titulo}' no estaba prestado.");
}
}
// Método que usa las propiedades
public void MostrarInformacion()
{
Console.WriteLine($"Título: {Titulo}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {ISBN}");
Console.WriteLine($"Páginas: {Paginas}");
Console.WriteLine($"Estado: {(EstaDisponible ? "Disponible" : "Prestado")}");
if (fechaPrestamo.HasValue)
Console.WriteLine($"Fecha de préstamo: {fechaPrestamo.Value.ToShortDateString()}");
}
}
Y así es como se utilizaría esta clase:
class Program
{
static void Main()
{
try
{
Libro libro = new Libro(
"El Quijote",
"Miguel de Cervantes",
"9788420412146",
863);
libro.MostrarInformacion();
libro.Prestar();
Console.WriteLine($"¿Está disponible? {libro.EstaDisponible}");
Console.WriteLine($"Fecha de préstamo: {libro.FechaPrestamo}");
libro.Devolver();
Console.WriteLine($"¿Está disponible? {libro.EstaDisponible}");
// Esto generaría una excepción:
// libro.ISBN = "123"; // Error: El ISBN debe tener 13 caracteres
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
En este ejemplo, las propiedades nos permiten:
- Validar datos (título, autor, ISBN, páginas)
- Proporcionar propiedades de solo lectura (EstaPrestado, FechaPrestamo)
- Crear propiedades calculadas (EstaDisponible)
- Encapsular la lógica de préstamo y devolución en métodos específicos
Este enfoque garantiza que el objeto Libro
siempre se mantenga en un estado válido y que las operaciones que modifican su estado se realicen de manera controlada.
Propiedades auto-implementadas
Las propiedades auto-implementadas son una característica de C# que simplifica enormemente la sintaxis para crear propiedades cuando no se necesita lógica adicional en los accesores get y set. Introducidas en C# 3.0, estas propiedades permiten declarar propiedades sin tener que definir explícitamente un campo de respaldo.
Sintaxis básica
La sintaxis de una propiedad auto-implementada es mucho más concisa que la de una propiedad tradicional:
// Propiedad auto-implementada
public tipo NombrePropiedad { get; set; }
Cuando se utiliza esta sintaxis, el compilador de C# genera automáticamente un campo privado de respaldo y proporciona implementaciones básicas para los métodos get y set. Este campo generado no es directamente accesible en el código.
Comparación con propiedades tradicionales
Para entender mejor las ventajas de las propiedades auto-implementadas, comparemos ambos enfoques:
Propiedad tradicional:
// Campo privado de respaldo
private string nombre;
// Propiedad con implementación explícita
public string Nombre
{
get { return nombre; }
set { nombre = value; }
}
Propiedad auto-implementada:
// Propiedad auto-implementada (el campo de respaldo es generado por el compilador)
public string Nombre { get; set; }
Como puedes ver, la versión auto-implementada es mucho más concisa y requiere menos código, lo que hace que las clases sean más limpias y fáciles de leer.
Cuándo usar propiedades auto-implementadas
Las propiedades auto-implementadas son ideales cuando:
- No necesitas lógica adicional en los accesores get o set
- No requieres validaciones al asignar valores
- No necesitas transformar los datos al leerlos o escribirlos
- Simplemente quieres encapsular un campo con accesores básicos
Ejemplo práctico
Veamos un ejemplo de una clase Persona
que utiliza propiedades auto-implementadas:
public class Persona
{
// Propiedades auto-implementadas
public string Nombre { get; set; }
public string Apellido { get; set; }
public int Edad { get; set; }
// Propiedad calculada (no auto-implementada)
public string NombreCompleto
{
get { return $"{Nombre} {Apellido}"; }
}
// Constructor
public Persona(string nombre, string apellido, int edad)
{
Nombre = nombre;
Apellido = apellido;
Edad = edad;
}
}
Y así es como se utilizaría:
Persona persona = new Persona("Juan", "Pérez", 30);
Console.WriteLine(persona.NombreCompleto); // Muestra "Juan Pérez"
// Modificar una propiedad
persona.Edad = 31;
Propiedades auto-implementadas de solo lectura
A partir de C# 6.0, puedes crear propiedades auto-implementadas de solo lectura omitiendo el modificador set
:
public class Producto
{
// Propiedad auto-implementada de solo lectura
public string Codigo { get; }
// Propiedades auto-implementadas normales
public string Nombre { get; set; }
public decimal Precio { get; set; }
public Producto(string codigo)
{
// Las propiedades de solo lectura solo pueden asignarse en el constructor
Codigo = codigo;
}
}
En este ejemplo, Codigo
es una propiedad de solo lectura que solo puede asignarse en el constructor. Después de la inicialización del objeto, su valor no puede cambiar.
Inicialización de propiedades auto-implementadas
Desde C# 6.0, también puedes inicializar propiedades auto-implementadas directamente en su declaración:
public class Configuracion
{
// Propiedades auto-implementadas con valores iniciales
public bool ModoOscuro { get; set; } = false;
public string Idioma { get; set; } = "Español";
public int TamañoFuente { get; set; } = 12;
// Propiedad de solo lectura con valor inicial
public DateTime FechaCreacion { get; } = DateTime.Now;
}
Esta característica es especialmente útil para establecer valores predeterminados sin tener que hacerlo en el constructor.
Modificadores de acceso en accesores
También puedes aplicar diferentes modificadores de acceso a los accesores get y set en propiedades auto-implementadas:
public class Cliente
{
// Propiedad con getter público y setter privado
public string Id { get; private set; }
// Propiedad con getter y setter públicos
public string Nombre { get; set; }
public Cliente(string id, string nombre)
{
Id = id; // Podemos asignar aquí porque estamos dentro de la clase
Nombre = nombre;
}
public void GenerarNuevoId()
{
// Podemos modificar Id dentro de la clase
Id = Guid.NewGuid().ToString();
}
}
En este ejemplo, el Id
del cliente solo puede modificarse dentro de la clase Cliente
, mientras que Nombre
puede modificarse desde cualquier lugar.
Ejemplo completo: Sistema de gestión de inventario
Veamos un ejemplo más completo que muestra cómo las propiedades auto-implementadas pueden simplificar el código en una aplicación real:
public class Articulo
{
// Propiedades auto-implementadas de solo lectura
public string Codigo { get; }
public DateTime FechaRegistro { get; }
// Propiedades auto-implementadas normales
public string Nombre { get; set; }
public string Descripcion { get; set; }
public decimal Precio { get; set; }
// Propiedad auto-implementada con setter privado
public int Stock { get; private set; }
// Constructor
public Articulo(string codigo, string nombre, decimal precio, int stockInicial)
{
Codigo = codigo;
FechaRegistro = DateTime.Now;
Nombre = nombre;
Precio = precio;
Stock = stockInicial;
}
// Métodos que modifican el stock
public bool RetirarStock(int cantidad)
{
if (cantidad <= 0)
{
Console.WriteLine("La cantidad a retirar debe ser positiva");
return false;
}
if (cantidad > Stock)
{
Console.WriteLine("No hay suficiente stock disponible");
return false;
}
Stock -= cantidad;
Console.WriteLine($"Stock actualizado: {Stock} unidades");
return true;
}
public void AgregarStock(int cantidad)
{
if (cantidad <= 0)
{
Console.WriteLine("La cantidad a agregar debe ser positiva");
return;
}
Stock += cantidad;
Console.WriteLine($"Stock actualizado: {Stock} unidades");
}
// Método que muestra información del artículo
public void MostrarInformacion()
{
Console.WriteLine($"Código: {Codigo}");
Console.WriteLine($"Nombre: {Nombre}");
Console.WriteLine($"Descripción: {Descripcion ?? "No disponible"}");
Console.WriteLine($"Precio: {Precio:C}");
Console.WriteLine($"Stock: {Stock} unidades");
Console.WriteLine($"Fecha de registro: {FechaRegistro}");
}
}
Y así es como se utilizaría esta clase:
// Crear un nuevo artículo
Articulo telefono = new Articulo(
"TECH-001",
"Smartphone XYZ",
599.99m,
10);
// Establecer descripción
telefono.Descripcion = "Smartphone de última generación con 128GB de almacenamiento";
// Mostrar información
telefono.MostrarInformacion();
// Retirar stock
telefono.RetirarStock(3);
// Agregar stock
telefono.AgregarStock(5);
// Esto no sería posible debido al setter privado:
// telefono.Stock = 100; // Error: El setter de 'Stock' es inaccesible
// Esto no sería posible debido a que son propiedades de solo lectura:
// telefono.Codigo = "TECH-002"; // Error: La propiedad 'Codigo' es de solo lectura
// telefono.FechaRegistro = DateTime.Now; // Error: La propiedad 'FechaRegistro' es de solo lectura
Ventajas de las propiedades auto-implementadas
Las propiedades auto-implementadas ofrecen varias ventajas importantes:
- Código más limpio: Reducen significativamente la cantidad de código repetitivo.
- Menos errores: Al no tener que escribir manualmente los campos de respaldo, se eliminan posibles errores de tipeo.
- Mantenibilidad: Si más adelante necesitas agregar lógica a una propiedad, puedes convertirla en una propiedad completa sin cambiar la interfaz pública.
- Encapsulación: Siguen proporcionando los beneficios de encapsulación de las propiedades tradicionales.
Cuándo evitar propiedades auto-implementadas
Aunque son muy útiles, hay situaciones donde no deberías usar propiedades auto-implementadas:
- Cuando necesitas validar los datos antes de asignarlos
- Cuando necesitas notificar a otros componentes cuando cambia un valor
- Cuando necesitas transformar los datos al leerlos o escribirlos
- Cuando necesitas lógica personalizada en los accesores get o set
En estos casos, deberías usar propiedades completas con implementación explícita.
Migración de campos a propiedades
Una ventaja importante de las propiedades auto-implementadas es que facilitan la migración de campos públicos a propiedades sin romper el código existente:
// Versión 1: Usando campos públicos (mala práctica)
public class ClienteV1
{
public string Nombre;
public string Email;
}
// Versión 2: Migración a propiedades auto-implementadas (mejor práctica)
public class ClienteV2
{
public string Nombre { get; set; }
public string Email { get; set; }
}
El código que usa ClienteV1
seguirá funcionando sin cambios con ClienteV2
, pero ahora tienes la flexibilidad de agregar lógica a las propiedades en el futuro si es necesario.
Propiedades auto-implementadas vs campos públicos
Para ilustrar por qué las propiedades auto-implementadas son mejores que los campos públicos, consideremos este ejemplo:
// Enfoque 1: Usando campos públicos (no recomendado)
public class VehiculoMalo
{
public string Marca;
public int Año;
public int Kilometraje;
}
// Uso:
VehiculoMalo coche = new VehiculoMalo();
coche.Marca = "Toyota";
coche.Año = 2020;
coche.Kilometraje = -5000; // ¡Permitido pero incorrecto!
Si más tarde necesitamos agregar validación para el kilometraje, tendríamos que cambiar el campo por una propiedad, lo que podría romper el código existente.
Enfoque 2: Usando propiedades auto-implementadas (recomendado)
public class VehiculoBueno
{
public string Marca { get; set; }
public int Año { get; set; }
public int Kilometraje { get; set; }
}
// Uso:
VehiculoBueno coche = new VehiculoBueno();
coche.Marca = "Toyota";
coche.Año = 2020;
coche.Kilometraje = -5000; // Aún permitido, pero podemos agregar validación después
Si más tarde necesitamos agregar validación, podemos convertir la propiedad auto-implementada en una propiedad completa sin cambiar la interfaz pública:
public class VehiculoMejorado
{
public string Marca { get; set; }
public int Año { get; set; }
// Campo privado de respaldo (ahora explícito)
private int kilometraje;
// Propiedad completa con validación
public int Kilometraje
{
get { return kilometraje; }
set
{
if (value >= 0)
kilometraje = value;
else
throw new ArgumentException("El kilometraje no puede ser negativo");
}
}
}
El código que usa VehiculoBueno
seguirá funcionando con VehiculoMejorado
, excepto que ahora se validará el kilometraje.
Ejercicios de esta lección Propiedades y encapsulación
Evalúa tus conocimientos de esta lección Propiedades y encapsulación con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
CRUD en C# de modelo Customer sobre una lista
Arrays y listas
Objetos
Excepciones
Eventos
Lambdas
Diccionarios en C#
Variables y constantes
Tipos de datos
Herencia
Operadores
Uso de consultas LINQ
Clases y encapsulación
Uso de consultas LINQ
Excepciones
Control de flujo
Eventos
Diccionarios
Tipos de datos
Conjuntos, colas y pilas
Lambdas
Conjuntos, colas y pilas
Uso de async y await
Tareas
Constructores y destructores
Operadores
Arrays y listas
Polimorfismo
Polimorfismo
Variables y constantes
Proyecto colecciones y LINQ en C#
Clases y encapsulación
Creación de proyecto C#
Uso de async y await
Funciones
Delegados
Delegados
Constructores y destructores
Objetos
Control de flujo
Funciones
Tareas
Proyecto sintaxis en C#
Herencia C Sharp
OOP en C Sharp
Diccionarios
Introducción a C#
Todas las lecciones de CSharp
Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A C#
Introducción Y Entorno
Creación De Proyecto C#
Introducción Y Entorno
Variables Y Constantes
Sintaxis
Tipos De Datos
Sintaxis
Operadores
Sintaxis
Control De Flujo
Sintaxis
Funciones
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Interpolación De Strings
Sintaxis
Estructuras De Control Condicional
Sintaxis
Manejo De Valores Nulos
Sintaxis
Clases Y Encapsulación
Programación Orientada A Objetos
Objetos
Programación Orientada A Objetos
Constructores Y Destructores
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Genéricos
Programación Orientada A Objetos
Métodos Virtuales Y Sobrecarga
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Propiedades Y Encapsulación
Programación Orientada A Objetos
Métodos De Extensión
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Clases Parciales
Programación Orientada A Objetos
Miembros Estáticos
Programación Orientada A Objetos
Tuplas Y Tipos Anónimos
Programación Orientada A Objetos
Arrays Y Listas
Colecciones Y Linq
Diccionarios
Colecciones Y Linq
Conjuntos, Colas Y Pilas
Colecciones Y Linq
Uso De Consultas Linq
Colecciones Y Linq
Linq Avanzado
Colecciones Y Linq
Colas Y Pilas
Colecciones Y Linq
Conjuntos
Colecciones Y Linq
Linq Básico
Colecciones Y Linq
Delegados Funcionales
Programación Funcional
Records
Programación Funcional
Expresiones Lambda
Programación Funcional
Linq Funcional
Programación Funcional
Fundamentos De La Programación Funcional
Programación Funcional
Pattern Matching
Programación Funcional
Testing Unitario Con Xunit
Testing
Excepciones
Excepciones
Delegados
Programación Asíncrona
Eventos
Programación Asíncrona
Lambdas
Programación Asíncrona
Uso De Async Y Await
Programación Asíncrona
Tareas
Programación Asíncrona
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender los diferentes modificadores de acceso en C# y su impacto en la visibilidad de miembros.
- Aprender a usar propiedades con getters y setters para encapsular campos privados.
- Diferenciar entre propiedades tradicionales y auto-implementadas y cuándo usar cada una.
- Implementar validaciones y lógica en propiedades para mantener la integridad de los datos.
- Aplicar encapsulación para proteger el estado interno de los objetos y mejorar la mantenibilidad del código.