Создать общий метод, ограничивающий перечисление

1190

Я строю функцию для расширения Enum.Parseконцепции, которая

  • Позволяет проанализировать значение по умолчанию в случае, если значение Enum не найдено
  • Нечувствителен к регистру

Поэтому я написал следующее:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Я получаю ошибку Ограничение не может быть специальным классом System.Enum.

Справедливо, но есть ли обходной путь, чтобы разрешить Generic Enum, или я собираюсь имитировать Parseфункцию и передавать тип в качестве атрибута, что вынуждает предъявлять к вашему уродливому требованию боксирование.

РЕДАКТИРОВАТЬ Все предложения ниже были высоко оценены, спасибо.

Поселились (я оставил цикл, чтобы сохранить нечувствительность к регистру - я использую это при разборе XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

РЕДАКТИРОВАТЬ: (16 февраля 2015 г.) Жюльен Лебосквейн недавно опубликовал приведенное ниже компиляторное универсальное безопасное решение с типобезопасным кодом в MSIL или F # , на которое стоит обратить внимание, и приветствие. Я уберу это редактирование, если решение будет пузыриться дальше на странице.

JohnC
источник
10
Может быть, вы должны использовать ToUpperInvariant () вместо ToLower () ...
Макс Галкин
31
@Shimmy: Как только вы передаете тип значения методу расширения, вы работаете над его копией, поэтому вы не можете изменить его состояние.
Гаро Йериазарян
4
Знайте, что это старый поток, не знаю, изменили ли они вещи, но методы расширения прекрасно работают для типов значений, конечно, они не всегда имеют такой же смысл, но я использовал "public static TimeSpan Seconds (this int x) { return TimeSpan.FromSeconds (x);} "чтобы включить синтаксис« Wait.For (5.Seconds ()) ... »
Jens
6
Поймите, что это не было частью вопроса, но вы могли бы улучшить логику цикла foreach, используя String.Equals с StringComparison.InvariantCultureIgnoreCase
Firestrand

Ответы:

1006

Поскольку EnumType реализует IConvertibleинтерфейс, лучшая реализация должна выглядеть примерно так:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Это все еще позволит передавать значения типов реализации IConvertible. Хотя шансы редки.

Вивек
источник
2
Обобщения доступны с версии .NET 2.0. Следовательно, они доступны и в VB 2005.
Вивек
46
Что ж, сделайте его еще более ограниченным, если вы решите пойти по этому пути ... используйте "класс TestClass <T>, где T: struct, IComparable, IFormattable, IConvertible"
Рикардо Нольде,
106
Другое предложение - определить универсальный тип с идентификатором TEnum. Таким образом: public TEnum GetEnumFromString <TEnum> (строковое значение), где TEnum: структура, IConvertible, IComparible, IFormattable {}
Лиза
11
Вы не получите много пользы, включив другие интерфейсы, потому что почти все встроенные типы значений реализуют все эти интерфейсы. Это особенно верно для ограничений на общий метод расширения, который чрезвычайно удобен для работы с перечислениями, за исключением того факта, что эти методы расширения похожи на вирус, который заражает все ваши объекты. IConvertable по крайней мере немного сужает его.
русский епископ
2
@SamIam: Когда вы писали, этой теме было 6 с половиной лет, и вы были правы, ни один из ответов не проверялся во время компиляции. Тогда только через 3 дня, через 6 лет, вы получили свое желание - см. Пост Жюльена Лебосквена ниже.
Дэвид И. Макинтош
663

Эта функция наконец поддерживается в C # 7.3!

Следующий фрагмент (из примеров dotnet ) демонстрирует, как:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Убедитесь, что в вашем проекте C # установлена ​​языковая версия версии 7.3.


Оригинальный ответ ниже:

Я опоздал на игру, но я воспринял это как вызов, чтобы посмотреть, как это можно сделать. Это не возможно в C # (или VB.NET, но прокрутите вниз для F #), но возможно в MSIL. Я написал эту маленькую .... вещь

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Который генерирует функцию, которая выглядела бы так, если бы это был допустимый C #:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Затем с помощью следующего кода C #:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

К сожалению, это означает, что эта часть вашего кода написана на MSIL вместо C #, и единственным дополнительным преимуществом является то, что вы можете ограничить этот метод System.Enum. Это также своего рода облом, потому что он собирается в отдельную сборку. Тем не менее, это не значит, что вы должны использовать его таким образом.

Удаляя строку .assembly MyThing{}и вызывая ilasm следующим образом:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

вы получаете сетевой модуль вместо сборки.

К сожалению, VS2010 (и ранее, очевидно) не поддерживает добавление ссылок сетевого модуля, что означает, что вам придется оставить его в 2 отдельных сборках при отладке. Единственный способ добавить их как часть вашей сборки - это запустить csc.exe самостоятельно, используя /addmodule:{files}аргумент командной строки. Это не было бы слишком больно в скрипте MSBuild. Конечно, если вы храбры или глупы, вы можете каждый раз запускать csc самостоятельно. И это, безусловно, усложняется, поскольку доступ к нему требуется нескольким сборкам.

Таким образом, это может быть сделано в .Net. Это стоит дополнительных усилий? Ну, я думаю, я позволю тебе определиться с этим.


Решение F # как альтернатива

Дополнительный кредит: оказывается, что общее ограничение enumвозможно по крайней мере на одном другом языке .NET, кроме MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Этот вариант проще поддерживать, поскольку он является хорошо известным языком с полной поддержкой Visual Studio IDE, но для него вам все равно нужен отдельный проект. Тем не менее, он , естественно , производит значительно отличается IL (код является очень разные) и опирается наFSharp.Core библиотеку, которая, так же как и любой другой внешней библиотеки, должна стать частью вашего дистрибутива.

Вот как вы можете использовать его (в основном то же самое, что и решение MSIL), и показать, что оно корректно завершается ошибкой в ​​других синонимичных структурах:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);
Кристофер Токов
источник
67
Да, очень хардкор. Я очень уважаю того, кто умеет кодировать на IL и знает, как эти функции поддерживаются на более высоком уровне языка - уровне, который многие из нас все еще считают низким на уровне приложений, бизнес-правил, пользовательского интерфейса, библиотек компонентов и т. Д. .
TonyG
13
То, что я действительно хотел бы знать, - то, почему команда C # еще не начала позволять это, так как это уже поддерживается MSIL.
MgSam
25
@MgSam - От Эрика Липперта :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Кристофер Уркс
5
@LordofScripts: Я думаю , что причина в том , что , что , поскольку класс , который сдерживает , Tчтобы System.Enumне быть в состоянии сделать все, что с тем, Tчто люди могли бы ожидать, авторы C # полагал , что они , возможно, также запретить его полностью. Я считаю это решение неудачным, поскольку в C # он просто игнорировал какую-либо специальную обработку System.Enumограничений, можно было бы написать HasAnyFlags<T>(this T it, T other)метод расширения, который был бы на несколько порядков быстрее Enum.HasFlag(Enum)и проверял бы его аргументы.
суперкат
9
Я не думаю, что у меня когда-либо был проект, где я не оказался здесь. C # 6 - это 110% синтаксический сахар, и ЭТО не попало? Отрежь дерьмо.
Майкл Блэкберн
214

C # ≥ 7.3

Начиная с C # 7.3 (доступно с Visual Studio 2017 ≥ v15.7), этот код теперь полностью действителен:

public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, Enum
{
 ...
}

C # ≤ 7,2

Вы можете получить реальное принудительное ограничение перечисления путем использования наследования ограничений. Следующий код определяет одновременно classи a, и structограничения:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Применение:

EnumUtils.Parse<SomeEnum>("value");

Примечание: это специально указано в спецификации языка C # 5.0:

Если параметр типа S зависит от параметра типа T, то: [...] Допустимо, чтобы S имел ограничение типа значения, а T - ограничение ссылочного типа. Фактически это ограничивает T типами System.Object, System.ValueType, System.Enum и любым типом интерфейса.

Жюльен Лебосквен
источник
7
@ DavidI.McIntosh EnumClassUtils<System.Enum>достаточно, чтобы ограничить T любыми System.Enumи любыми производными типами. structна Parseто , ограничивает его дальше к реальному перечислимого типа. Вы должны ограничить Enumв какой-то момент. Для этого ваш класс должен быть вложенным. См. Gist.github.com/MrJul/7da12f5f2d6c69f03d79
Жюльен Лебосквейн
7
Просто чтобы быть понятным, мой комментарий «не понравился» не был комментарием к вашему решению - это действительно красивый взлом. Просто «не приятно», что MS заставляет нас использовать такой извилистый взлом.
Дэвид И. Макинтош
2
Есть ли способ сделать это, чтобы также быть полезным для методов расширения?
Морд Цубер
3
Что здесь дает where TClass : classограничение?
Цемер
2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm
30

редактировать

На этот вопрос теперь великолепно ответил Жюльен Лебосквейн . Я также хотел бы выразить свой ответ с ignoreCase, defaultValueи необязательными аргументами, в то время как добавление TryParseи ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Примеры использования:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

старый

Мои старые улучшения в ответе Вивека с использованием комментариев и «новых» разработок:

  • использование TEnum для ясности для пользователей
  • добавить больше интерфейсных ограничений для дополнительной проверки ограничений
  • давай TryParseсправимсяignoreCase существующий параметр (введен в VS2010 / .Net 4)
  • опционально используйте родовое defaultзначение (введено в VS2005 / .Net 2)
  • использовать необязательные аргументы (представленные в VS2010 / .Net 4) со значениями по умолчанию для defaultValueиignoreCase

в результате чего:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}
Серьезный Yahoo
источник
18

Вы можете определить статический конструктор для класса, который проверит, что тип T является перечислением, и выдаст исключение, если это не так. Этот метод упоминается Джеффри Рихтером в его книге CLR через C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Затем в методе разбора вы можете просто использовать Enum.Parse (typeof (T), input, true) для преобразования строки в перечисление. Последний истинный параметр предназначен для игнорирования регистра ввода.

Карг
источник
1
Это хороший вариант для универсальных классов - но, конечно, это не помогает для универсальных методов.
МакГарнагл
Кроме того, это также не применяется во время компиляции, вы будете знать, что предоставили non только Enum Tпри выполнении конструктора. Хотя это гораздо приятнее, чем ждать конструктора экземпляра.
JRH
15

Следует также учитывать, что, поскольку выпуск C # 7.3 с использованием ограничений Enum поддерживается "из коробки", не требуя дополнительной проверки и прочего.

Итак, если вы изменили языковую версию вашего проекта на C # 7.3, следующий код будет работать отлично:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

Если вы не знаете, как изменить языковую версию на C # 7.3, см. Следующий снимок экрана: введите описание изображения здесь

РЕДАКТИРОВАТЬ 1 - Требуемая версия Visual Studio и учитывая ReSharper

Чтобы Visual Studio мог распознать новый синтаксис, вам нужна как минимум версия 15.7. Вы также можете найти это в примечаниях к выпуску Microsoft, см. Замечания к выпуску Visual Studio 2017 15.7 . Спасибо @MohamedElshawaf за указание на этот действительный вопрос.

Просьба также заметить, что в моем случае ReSharper 2018.1 на момент написания этой EDIT еще не поддерживает C # 7.3. После активации ReSharper ограничение Enum выделяется как ошибка, сообщающая, что я не могу использовать 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' в качестве ограничения параметра типа . ReSharper предлагает в качестве быстрого исправления удалить ограничение 'Enum' для параметра типа T метода

Однако, если вы временно отключите ReSharper в Сервис -> Параметры -> ReSharper Ultimate -> Общие, вы увидите, что синтаксис отлично работает, если вы используете VS 15.7 или выше и C # 7.3 или выше.

baumgarb
источник
1
Какую версию VS вы используете?
mshwf
1
@MohamedElshawaf Я полагаю, что это версия 15.7, которая содержит поддержку C # 7.3
Патрик Робертс
1
Я думаю, что лучше написать where T : struct, Enum, чтобы не передавать System.Enumсебя в качестве параметра типа.
Мариуш Павельски
Как @MariuszPawelski я пишу struct, Enum. Мое обоснование объясняется в ответе и комментариях здесь .
Стивен Кеннеди
Информация ReSharper действительно помогла мне. Обратите внимание, последняя версия предварительного просмотра поддерживает эту функцию.
DalSoft
11

Я модифицировал образец от dimarzionist. Эта версия будет работать только с Enums и не пропускать структуры.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Bivoauc
источник
13
Я не вернул бы значение по умолчанию при неудаче; Я бы позволил распространению исключения (так же, как это происходит с Enum.Parse). Вместо этого используйте TryParse, возвращающий bool, и возвращайте результат, используя выходной параметр.
Марк Симпсон
1
ОП хочет, чтобы он не учитывал регистр, это не так.
Конрад Моравский
9

Я попытался немного улучшить код:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}
Мартин
источник
1
Это лучше, чем принятый ответ, потому что он позволяет вам звонить, defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)даже если вы не знаете, какой это тип перечисления, только то, что объект является перечислением.
Styfle
1
Тем не IsDefinedменее, предварительная проверка с помощью уничтожит нечувствительность к регистру. В отличие от Parse, IsDefinedне имеет ignoreCaseаргументов, и MSDN говорит, что это только точный случай .
Nyerguds
5

У меня есть конкретное требование, когда я должен использовать enum с текстом, связанным со значением enum. Например, когда я использую enum для указания типа ошибки, требуется описать детали ошибки.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}
Солнечный Раджвади
источник
4

Надеюсь, что это полезно:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}
dimarzionist
источник
1
Если вам нужна нечувствительность к регистру, просто замените return (TValue)Enum.Parse(typeof (TValue), value);наreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos
3

Интересно, что это возможно в других языках (Managed C ++, IL напрямую).

Цитировать:

... Оба ограничения фактически создают действительный IL и могут также использоваться C #, если они написаны на другом языке (вы можете объявить эти ограничения в управляемом C ++ или в IL).

Кто знает

Эндрю Бакер
источник
2
Управляемые расширения для C ++ не имеют НИКАКОЙ поддержки обобщений, я думаю, вы имеете в виду C ++ / CLI.
Бен Фойгт
3

Это мое мнение. В сочетании с ответами и MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

MSDN Source

KarmaEDV
источник
2
Это на самом деле не имеет смысла. Если на TEnumсамом деле это тип Enum, но textпустая строка, вы получите ArgumentExceptionвыражение «TEnum должно быть типом Enum», даже если это так.
Ник
3

Существующие ответы верны на C # <= 7.2. Однако существует запрос функции языка C # (связанный с запросом функции corefx ), чтобы разрешить следующее;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

На момент написания статьи функция «В обсуждении» на собраниях по развитию языка.

РЕДАКТИРОВАТЬ

Согласно информации nawfal , это вводится в C # 7.3 .

DiskJunky
источник
1
Интересная дискуссия там, спасибо. Ничего не
заложено
1
@johnc, очень верно , но стоит записка и это часто задаваемый особенность.
Чёрные
1
Это происходит в C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
nawfal
1

Мне всегда нравилось это (вы можете изменить по мере необходимости):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
Джефф
источник
1

Мне понравилось решение Christopher Currens, использующее IL, но для тех, кто не хочет заниматься сложными задачами, связанными с включением MSIL в процесс сборки, я написал аналогичную функцию на C #.

Обратите внимание, что вы не можете использовать общее ограничение, например, where T : Enumпотому что Enum является специальным типом. Поэтому я должен проверить, действительно ли данный универсальный тип является enum.

Моя функция:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
эксперт
источник
1

Я инкапсулировал решение Vivek в служебный класс, который вы можете использовать повторно. Обратите внимание, что вы все равно должны определить ограничения типа «где T: struct, IConvertible» для вашего типа.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
niaher
источник
1

Я создал расширение метода to get integer value from enum взглянуть на реализацию метода

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

это использование

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Башир Аль-Момани
источник
Хотя это, вероятно, работает, это почти не имеет отношения к вопросу.
Кетцалькоатль
1

Как указано в других ответах ранее; хотя это не может быть выражено в исходном коде, на самом деле это может быть сделано на уровне IL. @Christopher Currens ответ показывает, как IL делает с этим.

С помощью надстройки Fody ExtraConstraints.Fody для этого есть очень простой способ, в комплекте с инструментами сборки. Просто добавьте свои пакеты nuget (Fody , ExtraConstraints.Fody) в ваш проект и добавьте ограничения следующим образом (выдержка из Readme of ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

и Фоди добавит необходимый IL для присутствия ограничения. Также обратите внимание на дополнительную функцию ограничения делегатов:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Что касается Enums, вы также можете обратить внимание на очень интересный Enums.NET .

BatteryBackupUnit
источник
1

Это моя реализация. В принципе, вы можете настроить любой атрибут, и он работает.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }
Cubelaster
источник
0

Если потом можно использовать прямое приведение, я думаю, вы можете использовать System.Enumбазовый класс в своем методе, где это необходимо. Вам просто нужно аккуратно заменить параметры типа. Таким образом, реализация метода будет выглядеть так:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Тогда вы можете использовать его как:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
uluorta
источник
использование Enum.ToObject()даст более гибкий результат. Кроме того, вы можете выполнять сравнения строк без учета регистра, что ToLower()
исключает
-6

Просто для полноты, следующее решение Java. Я уверен, что то же самое можно сделать и в C #. Это избавляет от необходимости указывать тип где-либо в коде - вместо этого вы указываете его в строках, которые вы пытаетесь проанализировать.

Проблема в том, что нет никакого способа узнать, какому перечислению может соответствовать строка - поэтому ответ состоит в том, чтобы решить эту проблему.

Вместо того, чтобы принимать только строковое значение, примите String, которая имеет и перечисление, и значение в форме "enumeration.value". Рабочий код ниже - требуется Java 1.8 или новее. Это также сделает XML более точным, так как вы увидите что-то вроде color = "Color.red" вместо просто color = "red".

Вы бы вызвали метод acceptEnumeratedValue () со строкой, содержащей имя точечного значения имени перечисления.

Метод возвращает формальное перечисляемое значение.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Родни П. Барбати
источник