Mejores Prácticas
## Objetivos de Aprendizaje
- Escribir código C# limpio y mantenible
- Comprender la recolección de basura
- Introducción a la programación asíncrona
- Seguir convenciones de nomenclatura
## Convenciones de Nomenclatura
### PascalCase
```csharp
// Clases, métodos, propiedades, eventos
public class BankAccount
{
public string AccountNumber { get; set; }
public decimal Balance { get; set; }
public event EventHandler? BalanceChanged;
public void Deposit(decimal amount) { }
}
```
### camelCase
```csharp
// Variables locales, parámetros
public void CalculateTotal(int firstNumber, int secondNumber)
{
decimal totalAmount = firstNumber + secondNumber;
}
```
### Campos Privados
```csharp
class Person
{
private string _name; // Prefijo de guión bajo (recomendado)
private int _age;
// O
private string name; // Sin prefijo
private int age;
}
```
### Constantes
```csharp
// const - PascalCase
public const int MaxRetryCount = 3;
public const string DefaultCulture = "en-US";
// readonly - _camelCase
private readonly ILogger _logger;
```
## Organización del Código
### Organización de Namespaces
```csharp
namespace MyApp.Services
{
public class OrderService { }
}
namespace MyApp.Models
{
public class Order { }
}
namespace MyApp.Repositories
{
public class OrderRepository { }
}
```
### Organización de Archivos
```csharp
// Una clase pública por archivo (recomendado)
// El nombre del archivo coincide con la clase
// Order.cs contiene la clase Order
```
### Directivas Using
```csharp
// System namespaces primero
using System;
using System.Collections.Generic;
using System.Linq;
// Luego de terceros
using Newtonsoft.Json;
// Luego del proyecto
using MyApp.Models;
using MyApp.Services;
```
### Using Global (C# 10+)
```csharp
// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
```
## Recolección de Basura
### Cómo Funciona GC
```csharp
// Objetos sin referencias son elegibles para recolección
class Program
{
static void Main()
{
var person = new Person("Alice");
person = null; // Ahora elegible para GC
GC.Collect(); // Forzar recolección (no recomendado)
GC.WaitForPendingFinalizers();
}
}
```
### Patrón IDisposable
```csharp
class ResourceHolder : IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Liberar recursos administrados
}
// Liberar recursos no administrados
_disposed = true;
}
}
~ResourceHolder() // Finalizador
{
Dispose(false);
}
}
// Uso
using (var holder = new ResourceHolder())
{
// Usar holder
} // Disposed automáticamente
```
### Instrucción using
```csharp
// Asegurar disposición
using (var reader = new StreamReader("file.txt"))
using (var writer = new StreamWriter("output.txt"))
{
// Usar recursos
} // Ambos disposed
// C# 8+ simplificado
using var reader = new StreamReader("file.txt");
string content = reader.ReadToEnd();
// Disposed al final del alcance
```
## Programación Asíncrona
### async y await
```csharp
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Iniciando...");
await LongRunningOperation();
Console.WriteLine("¡Hecho!");
}
static async Task LongRunningOperation()
{
await Task.Delay(1000); // Simular trabajo
Console.WriteLine("Operación completada");
}
}
```
### Devolver Valores
```csharp
static async Task CalculateSumAsync(int a, int b)
{
await Task.Delay(100); // Simular trabajo asíncrono
return a + b;
}
// Uso
int result = await CalculateSumAsync(5, 3);
Console.WriteLine(result); // 8
```
### Async vs Síncrono
```csharp
// Síncrono (bloquea)
string content = File.ReadAllText("file.txt");
// Asíncrono (no bloquea)
async Task ReadFileAsync()
{
return await File.ReadAllTextAsync("file.txt");
}
// Uso
string content = await ReadFileAsync();
```
### No Usar async void
```csharp
// MAL - fire and forget, excepciones no pueden capturarse
async void BadMethod()
{
await Task.Delay(100);
throw new Exception("¡Ups!");
}
// BIEN - async Task
async Task GoodMethodAsync()
{
await Task.Delay(100);
throw new Exception("¡Ups!");
}
```
## Seguridad Null
### Tipos de Referencia Anulables (C# 8+)
```csharp
// No anulable por defecto (en contexto anulable)
string name = "Alice"; // No puede ser null
// name = null; // ¡Advertencia!
string? nullable = null; // Puede ser null
// Verificación de null requerida antes de usar
if (nullable != null)
{
Console.WriteLine(nullable.Length);
}
// Operador condicional nulo
int? length = nullable?.Length;
// Coalcencia nula
string safe = nullable ?? "default";
```
### Patrones para Null
```csharp
// Pattern matching con null
if (obj is string s && s.Length > 0)
{
Console.WriteLine(s);
}
// expresión switch con null
string Describe(object? obj) => obj switch
{
null => "null",
string s => $"Cadena: {s}",
int i => $"Int: {i}",
_ => "Algo más"
};
```
## Consejos de Rendimiento
### Construcción de Cadenas
```csharp
// Malo - crea muchas cadenas
string result = "";
for (int i = 0; i < 100; i++)
{
result += i.ToString();
}
// Bueno - StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(i);
}
string result = sb.ToString();
```
### Capacidad de List
```csharp
// Si conoces el tamaño
List numbers = new List(100); // Pre-allocar
for (int i = 0; i < 100; i++)
{
numbers.Add(i);
}
```
### Rendimiento de LINQ
```csharp
// Ejecución diferida
var query = collection.Where(x => x > 5); // No ejecutada todavía
// Ejecución forzada cuando se necesita
var list = query.ToList(); // Ejecuta
// Para datasets grandes, considerar
// - ToList() temprano para evitar enumeración múltiple
// - AsParallel() para consultas intensivas en CPU
```
## Mejores Prácticas de Manejo de Errores
```csharp
// Excepciones específicas primero
try
{
await ReadDataAsync();
}
catch (FileNotFoundException ex)
{
// Manejar específico
}
catch (IOException ex)
{
// Manejar IO
}
catch (Exception ex)
{
// Registrar errores inesperados
Logger.Error(ex);
throw;
}
// Filtros de excepciones para control granular
try
{
Process(data);
}
catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
{
// Manejar excepciones específicas
}
```
## Calidad del Código
### Principios SOLID
```csharp
// Responsabilidad Única
class UserService { void CreateUser() { } }
class EmailService { void SendEmail() { } }
// Abierto/Cerrado - extender, no modificar
abstract class Shape { public abstract double Area(); }
class Rectangle : Shape { public double Width, Height; public override double Area() => Width * Height; }
// Sustitución de Liskov
// Las clases derivadas deben ser usables como clase base
// Segregación de Interfaces
interface IReadable { void Read(); }
interface IWritable { void Write(); }
// Inversión de Dependencias
class OrderService
{
private readonly IRepository _repository;
public OrderService(IRepository repository) => _repository = repository;
}
```
### Atributos Útiles
```csharp
// Atributos comunes
[Obsolete("Usar NewMethod en su lugar")]
void OldMethod() { }
[DebuggerDisplay("Name = {Name}")]
class Person { public string Name { get; set; } }
[Serializable]
class Data { }
// Anulable
#nullable enable
class MyClass
{
public string? NullableProperty { get; set; }
}
#nullable restore
```
## Resumen
- Seguir convenciones de nomenclatura (PascalCase, camelCase)
- Usar global using (C# 10+) para archivos limpios
- Implementar IDisposable para recursos no administrados
- Preferir async/await para operaciones de E/S
- Habilitar tipos de referencia anulables
- Usar StringBuilder para concatenación de cadenas
- Manejar excepciones de lo específico a lo general
- Seguir principios SOLID
- Escribir código que se autodocumente con nombres claros
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →