Avatar ·

Как преобразовать строку в перечисление (enum) в C#?

Привет, коллеги! Я новичок в C# и столкнулся с проблемой. У меня есть перечисление (enum) для статусов заказа: ```csharp public enum OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled } ``` Я получаю статус от пользователя в виде строки, например, из текстового поля или из JSON-ответа от API. Строка может быть такой: "Pending", "Shipped" или "Cancelled". Мне нужно преобразовать эту строку в соответствующее значение перечисления, чтобы использовать его в логике программы. Я пробовал просто присвоить строку переменной типа `OrderStatus`, но компилятор выдаёт ошибку. Пытался использовать `Convert.ChangeType()`, но это тоже не сработало. Единственный способ, который я нашёл, — это написать огромный `switch` с кучей `case`: ```csharp OrderStatus status; switch (inputString) { case "Pending": status = OrderStatus.Pending; break; case "Processing": status = OrderStatus.Processing; break; // и так далее... } ``` Но это выглядит ужасно и неудобно, особенно если у меня много значений в перечислении. Наверняка есть более элегантное решение. Подскажите, как правильно конвертировать строку в enum в C#? И что делать, если строка не соответствует ни одному значению? Заранее спасибо за помощь! <<>>

Использование Enum.Parse() и Enum.TryParse()

В C# есть встроенные методы для преобразования строк в перечисления. Самый простой и распространённый способ — использовать статический метод Enum.Parse() или его безопасную версию Enum.TryParse().

1. Enum.Parse() — базовый вариант

Метод Enum.Parse() принимает тип перечисления и строку, а возвращает объект типа object, который нужно явно привести к нужному перечислению:

string input = "Shipped";
OrderStatus status = (OrderStatus)Enum.Parse(typeof(OrderStatus), input);
Console.WriteLine(status); // Вывод: Shipped

Этот метод чувствителен к регистру по умолчанию. Если строка не соответствует ни одному значению или написана с другой буквой (например, "shipped"), будет выброшено исключение ArgumentException. Чтобы игнорировать регистр, добавьте третий параметр ignoreCase: true:

string input = "shipped";
OrderStatus status = (OrderStatus)Enum.Parse(typeof(OrderStatus), input, ignoreCase: true);
Console.WriteLine(status); // Вывод: Shipped

2. Enum.TryParse() — безопасный вариант (рекомендуется)

Если вы не уверены, что строка всегда корректна, лучше использовать Enum.TryParse(). Он не выбрасывает исключение, а возвращает bool — успешно ли прошло преобразование. Результат сохраняется в выходной параметр:

string input = "Cancelled";
if (Enum.TryParse(input, out OrderStatus status))
{
    Console.WriteLine($"Статус: {status}");
}
else
{
    Console.WriteLine("Неверный статус!");
}

По умолчанию TryParse() также чувствителен к регистру. Чтобы сделать его нечувствительным, используйте соответствующую перегрузку:

string input = "cancelled";
if (Enum.TryParse(input, ignoreCase: true, out OrderStatus status))
{
    Console.WriteLine($"Статус: {status}"); // Вывод: Статус: Cancelled
}

3. Обработка числовых строк

Интересно, что Enum.TryParse() также может преобразовывать строки, содержащие числа. Например, если ваше перечисление имеет числовые значения (по умолчанию с 0), то строка "2" будет соответствовать OrderStatus.Shipped:

string input = "2";
if (Enum.TryParse(input, out OrderStatus status))
{
    Console.WriteLine(status); // Вывод: Shipped
}

Это может быть как удобно, так и опасно. Если вы хотите разрешить только строковые имена, а не числа, добавьте проверку через Enum.IsDefined():

string input = "2";
if (Enum.TryParse(input, out OrderStatus status) && Enum.IsDefined(typeof(OrderStatus), status))
{
    Console.WriteLine($"Корректный статус: {status}");
}
else
{
    Console.WriteLine("Неверное значение");
}

4. Работа с атрибутами [Description]

Иногда строки в вашей системе могут отличаться от имён в перечислении. Например, в JSON может прийти "in_progress" вместо "Processing". В таком случае помогает атрибут Description из пространства имён System.ComponentModel:

using System.ComponentModel;

public enum OrderStatus
{
    [Description("in_progress")]
    Processing,
    [Description("on_the_way")]
    Shipped,
    // ...
}

Для преобразования потребуется написать вспомогательный метод, который читает атрибут:

public static T GetEnumFromDescription<T>(string description) where T : Enum
{
    foreach (var field in typeof(T).GetFields())
    {
        if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
        {
            if (attribute.Description == description)
                return (T)field.GetValue(null);
        }
        else if (field.Name == description) // fallback на имя
        {
            return (T)field.GetValue(null);
        }
    }
    throw new ArgumentException($"Строка '{description}' не найдена в {typeof(T).Name}");
}

Использование:

string input = "in_progress";
OrderStatus status = GetEnumFromDescription<OrderStatus>(input);
Console.WriteLine(status); // Вывод: Processing

Рекомендация

Для большинства случаев используйте Enum.TryParse() с параметром ignoreCase: true и дополнительной проверкой через Enum.IsDefined(), если нужно исключить числовые строки. Это даёт чистый, безопасный и читаемый код без громоздких switch.

<<>>

Альтернативный подход: метод расширения (Extension Method)

Если вам часто приходится конвертировать строки в перечисления, удобно создать метод расширения для типа string. Это сделает код ещё более лаконичным:

public static class EnumExtensions
{
    public static T ToEnum<T>(this string value, bool ignoreCase = true) where T : Enum
    {
        return (T)Enum.Parse(typeof(T), value, ignoreCase);
    }

    public static bool TryToEnum<T>(this string value, out T result, bool ignoreCase = true) where T : Enum
    {
        return Enum.TryParse(value, ignoreCase, out result);
    }
}

Теперь преобразование выглядит очень просто:

string input = "delivered";
OrderStatus status = input.ToEnum<OrderStatus>();
Console.WriteLine(status); // Вывод: Delivered

// Безопасный вариант:
if (input.TryToEnum(out OrderStatus safeStatus))
{
    Console.WriteLine(safeStatus);
}

Этот подход особенно полезен, когда вы работаете с большим количеством перечислений в проекте. Метод расширения инкапсулирует логику и уменьшает дублирование кода. Единственный минус — нужно помнить о возможном исключении в ToEnum(), если строка некорректна, поэтому для пользовательского ввода всегда используйте TryToEnum().