Как преобразовать строку в enum в C#? (Enum.Parse, TryParse, игнорирование регистра)
Основное решение: Enum.Parse с обработкой исключений
Самый простой способ — использовать статический метод Enum.Parse(). Он преобразует строку в значение перечисления. Но, как вы правильно заметили, если строка не соответствует ни одному из имен элементов enum, будет выброшено исключение ArgumentException. Поэтому его нужно оборачивать в try-catch.
string userInput = "Shipped";
try
{
OrderStatus status = (OrderStatus)Enum.Parse(typeof(OrderStatus), userInput);
Console.WriteLine($"Статус: {status}");
}
catch (ArgumentException)
{
Console.WriteLine("Ошибка: введено некорректное значение статуса.");
}
Лучшая практика: Enum.TryParse (без исключений)
Начиная с .NET 4.0, появился метод Enum.TryParse(). Он не бросает исключение, а возвращает bool — успешно ли прошло преобразование. Это гораздо безопаснее и производительнее, так как исключения в C# дорогие.
string userInput = "Shipped";
if (Enum.TryParse(userInput, out OrderStatus status))
{
Console.WriteLine($"Статус: {status}");
}
else
{
Console.WriteLine("Ошибка: введено некорректное значение статуса.");
}
Игнорирование регистра (case-insensitive)
По умолчанию оба метода чувствительны к регистру. Если пользователь введет "shipped" (с маленькой буквы), преобразование не сработает. Чтобы это исправить, используйте параметр ignoreCase: true:
string userInput = "shipped"; // с маленькой буквы
// Вариант с TryParse с игнорированием регистра
if (Enum.TryParse(userInput, ignoreCase: true, out OrderStatus status))
{
Console.WriteLine($"Статус: {status}"); // Выведет: Shipped
}
// Вариант с Parse с игнорированием регистра
OrderStatus status2 = (OrderStatus)Enum.Parse(typeof(OrderStatus), userInput, ignoreCase: true);
Обработка числовых значений
Интересный момент: Enum.TryParse и Enum.Parse также могут преобразовывать строки, содержащие числа (например, "2" для Shipped, если индексы идут с 0). Это может быть как плюсом, так и источником ошибок. Если вы хотите разрешить ТОЛЬКО имена, а не числа, нужно добавить дополнительную проверку:
string userInput = "2";
if (Enum.TryParse(userInput, out OrderStatus status) && Enum.IsDefined(typeof(OrderStatus), status))
{
Console.WriteLine($"Статус: {status}"); // Сработает, если "2" — это Shipped
}
else
{
Console.WriteLine("Некорректное значение.");
}
Без проверки Enum.IsDefined даже строка "99" будет преобразована в значение 99, хотя такого элемента в enum нет. Это может привести к неожиданным багам.
Обобщенный метод-помощник (Generic helper)
Для удобства можно написать универсальный метод:
public static T ParseEnum<T>(string value, bool ignoreCase = true) where T : struct, Enum
{
if (Enum.TryParse(value, ignoreCase, out T result) && Enum.IsDefined(typeof(T), result))
{
return result;
}
throw new ArgumentException($"'{value}' не является допустимым значением для {typeof(T).Name}");
}
// Использование:
OrderStatus status = ParseEnum<OrderStatus>("shipped");
Итоговые рекомендации
- Всегда используйте
Enum.TryParse— он безопаснее и быстрее, чемEnum.Parseс try-catch. - Включайте
ignoreCase: true, если ваш ввод может быть в любом регистре (почти всегда так). - Проверяйте через
Enum.IsDefined, если вы не хотите разрешать числовые строки. - Для часто используемых преобразований вынесите логику в generic-метод.
Альтернативный подход: Использование атрибутов Description или пользовательских словарей
Иногда строки, которые вы получаете (из базы данных, API, пользовательского ввода), не совпадают с именами элементов enum. Например, в enum статус называется InProgress, а в базе данных хранится "В обработке" на русском. В таких случаях Enum.Parse не поможет.
Решение — использовать атрибут Description из пространства имен System.ComponentModel:
public enum OrderStatus
{
[Description("Ожидание")]
Pending,
[Description("В обработке")]
Processing,
[Description("Отправлено")]
Shipped
}
// Метод для получения enum по Description
public static T GetEnumFromDescription<T>(string description) where T : struct, Enum
{
foreach (var field in typeof(T).GetFields())
{
var attribute = field.GetCustomAttribute<DescriptionAttribute>();
if (attribute != null && attribute.Description == description)
{
return (T)field.GetValue(null);
}
}
throw new ArgumentException($"Описание '{description}' не найдено в {typeof(T).Name}");
}
// Использование:
OrderStatus status = GetEnumFromDescription<OrderStatus>("В обработке"); // Processing
Если атрибуты кажутся избыточными, можно просто создать статический словарь:
private static readonly Dictionary<string, OrderStatus> StatusMap = new(StringComparer.OrdinalIgnoreCase)
{
["ожидание"] = OrderStatus.Pending,
["в обработке"] = OrderStatus.Processing,
["отправлено"] = OrderStatus.Shipped
};
public static OrderStatus ParseFromRussian(string russianStatus)
{
if (StatusMap.TryGetValue(russianStatus, out var status))
return status;
throw new ArgumentException($"Неизвестный статус: {russianStatus}");
}
Этот подход дает полный контроль над маппингом и удобен для локализации.