En esta página
Clases y objetos en C# 14
Clases en C#
Una clase es un molde para crear objetos. Define la estructura (propiedades/campos) y el comportamiento (métodos) que tendrán las instancias creadas a partir de ella.
// Definición mínima de una clase
public class Persona
{
public string Nombre { get; set; } = "";
public int Edad { get; set; }
}
// Crear instancias
var p1 = new Persona { Nombre = "David", Edad = 30 };
var p2 = new Persona();
p2.Nombre = "María";
p2.Edad = 25;Modificadores de acceso
Controlan desde dónde se puede acceder a un miembro:
| Modificador | Accesible desde |
|---|---|
public |
En cualquier lugar |
private |
Solo dentro de la misma clase |
protected |
Clase + clases derivadas |
internal |
Dentro del mismo ensamblado |
protected internal |
Ensamblado o clases derivadas |
private protected |
Clase misma + derivadas en el mismo ensamblado |
Por defecto, los miembros de clase son private y las clases son internal.
Propiedades
Las propiedades en C# encapsulan los campos y permiten agregar validación:
public class CuentaBancaria
{
// Propiedad auto-implementada — el compilador genera el campo
public string Titular { get; set; } = string.Empty;
// Propiedad con validación personalizada
private decimal _saldo;
public decimal Saldo
{
get => _saldo;
private set // solo se puede establecer desde dentro de la clase
{
if (value < 0)
throw new InvalidOperationException("Saldo no puede ser negativo");
_saldo = value;
}
}
// Propiedad calculada (sin campo de respaldo)
public bool TieneSaldo => _saldo > 0;
// Propiedad con init — solo asignable en construcción
public string NumeroCuenta { get; init; } = Guid.NewGuid().ToString();
public CuentaBancaria(string titular, decimal saldoInicial)
{
Titular = titular;
Saldo = saldoInicial;
}
public void Depositar(decimal monto)
{
if (monto <= 0) throw new ArgumentException("El monto debe ser positivo");
Saldo += monto;
}
public void Retirar(decimal monto)
{
if (monto > Saldo) throw new InvalidOperationException("Fondos insuficientes");
Saldo -= monto;
}
}Constructores
Un constructor inicializa el objeto al crearlo:
public class Conexion
{
public string Host { get; }
public int Puerto { get; }
public bool UsarSsl { get; }
// Constructor principal
public Conexion(string host, int puerto, bool usarSsl = true)
{
Host = host;
Puerto = puerto;
UsarSsl = usarSsl;
}
// Constructor de conveniencia — llama al principal con :this()
public Conexion(string host) : this(host, 5432) { }
// Constructor estático — se ejecuta una sola vez cuando se carga el tipo
static Conexion()
{
Console.WriteLine("Tipo Conexion inicializado");
}
}
var conn1 = new Conexion("localhost", 1433, false);
var conn2 = new Conexion("db.bemorex.com"); // usa puerto 5432 y SSLConstructores primarios (C# 12+)
La sintaxis más concisa para clases simples:
// Sin constructor primario (C# clásico)
public class PuntoClasico
{
public double X { get; }
public double Y { get; }
public PuntoClasico(double x, double y) { X = x; Y = y; }
}
// Con constructor primario (C# 12+)
public class Punto(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
// Los parámetros x e y están disponibles en todo el cuerpo
public double DistanciaOrigen() => Math.Sqrt(x * x + y * y);
public override string ToString() => $"({x:F2}, {y:F2})";
}
var p = new Punto(3.0, 4.0);
Console.WriteLine(p); // (3.00, 4.00)
Console.WriteLine(p.DistanciaOrigen()); // 5Miembros estáticos
Los miembros estáticos pertenecen a la clase, no a las instancias:
public class Contador
{
private static int _total = 0;
public static int Total => _total;
public int Id { get; }
public Contador()
{
_total++;
Id = _total;
}
// Método de fábrica estático
public static Contador Crear() => new Contador();
// Método de utilidad estático
public static void Reiniciar() => _total = 0;
}
var c1 = Contador.Crear();
var c2 = Contador.Crear();
var c3 = Contador.Crear();
Console.WriteLine(Contador.Total); // 3Inicializadores de objeto
La sintaxis {} para inicializar propiedades sin constructor explícito:
public class DireccionPostal
{
public string Calle { get; set; } = string.Empty;
public string Ciudad { get; set; } = string.Empty;
public string Pais { get; set; } = "Bolivia";
public string CodigoPostal { get; set; } = string.Empty;
}
// Inicializador de objeto
var dir = new DireccionPostal
{
Calle = "Av. 6 de Agosto 123",
Ciudad = "Oruro",
// Pais usa el valor por defecto "Bolivia"
CodigoPostal = "OR-001"
};
// Objeto con init — solo inicializable así, inmutable después
public record Config
{
public string ApiUrl { get; init; } = string.Empty;
public int Timeout { get; init; } = 30;
public bool DebugMode { get; init; }
}
var cfg = new Config { ApiUrl = "https://api.bemorex.com", Timeout = 60 };
// cfg.Timeout = 90; // ← Error de compilación: init-only propertyClases parciales
Permiten dividir una clase en múltiples archivos:
// Archivo: Pedido.cs
public partial class Pedido
{
public int Id { get; set; }
public decimal Total { get; set; }
public string Cliente { get; set; } = string.Empty;
}
// Archivo: Pedido.Validacion.cs
public partial class Pedido
{
public bool EsValido() => Total > 0 && !string.IsNullOrEmpty(Cliente);
public void Validar()
{
if (!EsValido())
throw new InvalidOperationException("Pedido inválido");
}
}Clases sealed y abstract
// sealed — no se puede heredar
public sealed class Singleton
{
private static readonly Singleton _instancia = new();
public static Singleton Instancia => _instancia;
private Singleton() { }
}
// abstract — no se puede instanciar directamente
public abstract class Shape
{
public abstract double Area();
public abstract double Perimetro();
// Método concreto disponible para subclases
public string Describir() => $"Área: {Area():F2}, Perímetro: {Perimetro():F2}";
}Práctica
- Clase BancoCuenta: Implementa una clase con propiedades
Titular,Saldo(validado, no negativo) yNumeroCuenta(init-only). Agrega métodosDepositaryRetirarcon validaciones. - Constructor primario: Reescribe
Punto(double x, double y)con constructor primario y agrega un métodoMoverA(double dx, double dy)que retorne un nuevoPunto. - Miembro estático: Agrega un contador estático a tu clase
BancoCuentapara rastrear cuántas cuentas se han creado.
En la siguiente lección veremos herencia, clases abstractas, interfaces y cómo C# implementa el polimorfismo.
// Clase completa con todas las características modernas de C# 14
public class Producto
{
// Propiedades auto-implementadas
public int Id { get; init; } // init: solo en constructor/inicializador
public string Nombre { get; set; } = string.Empty;
public decimal Precio { get; set; }
// Propiedad calculada (computed)
public decimal PrecioConIVA => Precio * 1.13m;
// Campo privado con propiedad de validación
private int _stock;
public int Stock
{
get => _stock;
set
{
if (value < 0) throw new ArgumentException("Stock no puede ser negativo");
_stock = value;
}
}
// Miembro estático
public static int TotalCreados { get; private set; }
// Constructor
public Producto(int id, string nombre, decimal precio, int stock)
{
Id = id;
Nombre = nombre;
Precio = precio;
Stock = stock;
TotalCreados++;
}
// Sobrescribir ToString
public override string ToString()
=> $"[{Id}] {Nombre} — ${Precio:F2} (stock: {Stock})";
}
// Constructor primario (C# 12+)
public class Categoria(int id, string nombre)
{
public int Id { get; } = id;
public string Nombre { get; } = nombre;
public override string ToString() => $"Categoría: {Nombre}";
}
// Uso
var prod = new Producto(1, "Laptop Pro", 1299.99m, 10);
Console.WriteLine(prod);
Console.WriteLine($"IVA incluido: ${prod.PrecioConIVA:F2}");
Console.WriteLine($"Productos creados: {Producto.TotalCreados}");
var cat = new Categoria(1, "Electrónica");
Console.WriteLine(cat);
Inicia sesión para guardar tu progreso