Як розробник з багаторічним досвідом роботи з JavaScript і Node.js, я спочатку вважав, що обробка помилок у C# — це просто інша синтаксична форма для тих самих концептів. Однак реальність виявилася значно складнішою — від великої ієрархії виключень до механізмів серіалізації та конвенцій, що вимагають більш суворого підходу.
Перші кроки з виключеннями в C
Один з перших фрагментів коду, який привернув мою увагу при вивченні C#, був таким:
[Serializable]
public class CloudStorageException(
string message,
Exception? exception = null)
: MyCustomException($"Cloud storage error: {message}", exception)
{ }
Для когось, хто має досвід роботи з JavaScript, де виключення — це просто екземпляр класу Error
, наявність атрибута [Serializable] може здатися непотрібною. Однак у світі C# серіалізація виключень є критично важливою, особливо в розподілених додатках.
Для порівняння, у JavaScript ми б написали це значно простіше:
class CloudStorageException extends Error {
constructor(message, originalError = null) {
super(`Cloud storage error: ${message}`);
this.originalError = originalError;
}
}
Що означає Serializable?
Serializable в C# використовується для включення серіалізації — процесу перетворення об'єкта в формат, який можна зберігати (наприклад, на диску) або передавати (наприклад, по мережі), а потім відновити в його початкову форму через десеріалізацію.
Серіалізація виключень в C# дозволяє зберігати стан виключення так, щоб його можна було передавати, зберігати або відновлювати в іншому контексті. Це особливо корисно в розподілених системах, де виключення можуть передаватися між різними процесами чи машинами.
Роль Serializable
в серіалізації виключень
- Атрибут [Serializable] необхідний для того, щоб виключення можна було серіалізувати. Це стосується як вбудованих .NET виключень, так і користувацьких виключень, визначених розробником.
- Якщо клас виключення не позначений як Serializable, спроба його серіалізувати призведе до помилки під час виконання.
Коли використовувати Serializable для виключень?
Старі додатки .NET Framework вимагають використання атрибута [Serializable] для користувацьких класів виключень. Це особливо важливо для віддаленої комунікації, наприклад, коли старі додатки використовують Windows Communication Foundation (WCF). У таких випадках серіалізація виключень необхідна для належної роботи додатка.
Здається, що в сучасних додатках це вже не використовується, і замість цього надається перевага System.Text.Json. На жаль, я не знайшов чітке пояснення, яке я повністю розумію на цьому етапі. Якщо ви знаєте більше про це, будь ласка, поділіться в коментарях.
Ключові відмінності в підходах
Під час навчання я виявив кілька основних відмінностей між C# та JavaScript:
1. Ієрархія виключень
У C# всі виключення наслідують від класу System.Exception
, що забезпечує чітку структуру та категоризацію. JavaScript набагато більш гнучкий в цьому — виключенням може бути практично будь-який об'єкт.
2. Обробка помилок і їх поширення
На перший погляд, обробка помилок у C# і JavaScript/TypeScript може здаватися подібною. Однак реальні відмінності стають очевидними у складніших сценаріях.
Давайте розглянемо приклад обробки помилок у робочому процесі обробки замовлень:
// c#
public async Task ProcessOrderAsync(int orderId)
{
try
{
var order = await _orderRepository.GetOrderAsync(orderId);
await _paymentService.ProcessPaymentAsync(order);
}
catch (OrderNotFoundException ex)
{
// Спеціфічний тип виключення дозволяє точно обробити помилку
_logger.LogWarning(ex, "Order {OrderId} not found", orderId);
throw; // Зберігаємо оригінальний стек викликів
}
catch (PaymentException ex) when (ex.ErrorCode == PaymentErrorCode.InsufficientFunds)
{
// Використання патерн-матчингу у catch - унікальна особливість C#
await _notificationService.NotifyCustomerAboutPaymentIssue(orderId);
throw new OrderProcessingException("Payment failed", ex);
}
}
// TypeScript
async function processOrder(orderId: number): Promise {
try {
const order = await orderRepository.getOrder(orderId);
await paymentService.processPayment(order);
} catch (error) {
// Необхідно перевірити тип помилки під час виконання
if (error instanceof OrderNotFoundError) {
logger.warn(`Order ${orderId} not found`);
throw error;
}
// Перевірка деталей помилки менш елегантна
if (error instanceof PaymentError && error.code === 'INSUFFICIENT_FUNDS') {
await notificationService.notifyCustomerAboutPaymentIssue(orderId);
throw new OrderProcessingError('Payment failed', error);
}
// Зазвичай маємо загальний блок для невідомих помилок
throw error;
}
}
C# має кілька важливих переваг в обробці помилок:
- Патерн-матчинг у блоці
catch
за допомогою умовиwhen
— дозволяє елегантно та точно обробляти конкретні випадки. - Глибока інтеграція з типами — немає потреби перевіряти типи виключень під час виконання.
- Прогнозована поведінка стека викликів — простий
throw
зберігає оригінальний стек викликів. - Немає потреби в загальних блоках для обробки невідомих випадків.
Найкращі практики для ловлення виключень у C
До цього часу я вивчив кілька основних принципів обробки виключень у C#:
- Ловіть специфічні виключення — уникайте загального
catch (Exception)
на користь специфічних типів виключень. - Використовуйте блок
finally
— особливо важливо при управлінні ресурсами. - Створюйте спеціалізовані класи виключень — замість використання загального
Exception
, варто створювати спеціалізовані класи, як отCloudStorageException
.
Висновок
У цій статті я поділився своїм досвідом переходу від обробки помилок у JavaScript до C#. Ключові відмінності, які я виявив, включають:
- Більш суворий підхід до обробки помилок у C#, що вимагає точного оброблення конкретних типів виключень.
- Важливість атрибута [Serializable] у контексті ловлення виключень.
- Розширені механізми, такі як патерн-матчинг у блоках
catch
та прогнозована поведінка стека викликів.
Особливо цікавим для мене було те, як на перший погляд подібні механізми (try/catch
) можуть мати значні відмінності у реалізації та найкращих практиках між цими мовами.
Це лише початок мого шляху в C#. У майбутніх статтях я поділюся ще більше відкриттів та висновків. Якщо вам цікава ця тема, підписуйтеся, щоб побачити більше постів на Medium.
Перекладено з: Exception Handling in C#