Создать универсальный метод, ограничивающий T до Enum

Я создаю функцию, расширяющую концепцию 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 # ниже, на что стоит взглянуть и проголосовать за. Я удалю эту правку, если решение всплывет вверх по странице.

РЕДАКТИРОВАТЬ 2: (13 апреля 2021 г.) Поскольку теперь эта проблема решена и поддерживается, начиная с C # 7.3, я изменил принятый ответ, хотя полное прочтение основных ответов того стоит для ученых, и исторический, интересный :)


person johnc    schedule 17.09.2008    source источник
comment
Возможно, вам следует использовать ToUpperInvariant () вместо ToLower () ...   -  person Max Galkin    schedule 19.09.2008
comment
Почему методы расширения используются только для ссылочных типов?   -  person Shimmy Weitzhandler    schedule 18.08.2009
comment
@Shimmy: Как только вы передаете тип значения методу расширения, вы работаете с его копией, поэтому вы не можете изменить его состояние.   -  person Garo Yeriazarian    schedule 26.06.2010
comment
Знайте, что это старый поток, не знаю, изменили ли они что-то, но методы расширения отлично работают для типов значений, конечно, они не всегда могут иметь такой смысл, но я использовал общедоступные статические TimeSpan Seconds (this int x) {return TimeSpan.FromSeconds (x); }, чтобы включить синтаксис Wait.For (5.Seconds ()) ...   -  person Jens    schedule 06.03.2012
comment
Поймите, это не было частью вопроса, но вы можете улучшить логику цикла foreach, используя String.Equals с StringComparison.InvariantCultureIgnoreCase   -  person Firestrand    schedule 02.11.2012
comment
Зачем использовать этот foreach-цикл? Enum.Parse содержит ignoreCase-параметр (я думаю, поскольку .Net 2.0).   -  person Yahoo Serious    schedule 23.05.2013
comment
Я пошел дальше и добавил ответ с существующим ignoreCase-параметром и общим значением default в качестве дополнительных аргументов, а также некоторые другие улучшения. предложено другими.   -  person Yahoo Serious    schedule 24.05.2013
comment
Стоит отметить, что Enum.Parse может обрабатывать перечисление с атрибутом [Flags], при условии, что значения в строке разделяются запятыми.   -  person yoyo    schedule 22.11.2013
comment
Опубликуйте свое решение в виде ответа. Не включайте ответы в вопросы.   -  person BartoszKP    schedule 29.09.2015
comment
Как создается строка значения? Если это было создано с использованием метода Enum.ToString(), а тип Enum отмечен атрибутом [Flags], defaultValue всегда будет возвращаться из метода с value = (enum.type1 | enum.type2).ToString() == type1, type2. Угловой шкаф.   -  person paxmemento    schedule 16.10.2015
comment
проверьте этот ответ, написанный мной stackoverflow.com/a/38410351/4009642   -  person Ahmed Fwela    schedule 18.07.2016
comment
@ bigworld12 Изначальное требование, которое я должен был сделать для этого, давно потеряно в глубине веков, но, тем не менее, это очень комплексное решение :)   -  person johnc    schedule 25.07.2016
comment
Реализация этой функции находится в стадии разработки для C # 7! Проголосуйте! github.com/dotnet/roslyn/issues/262   -  person Airn5475    schedule 13.09.2016
comment
Очень старая тема, но после C # 7.3 произошли огромные улучшения. Теперь полностью поддерживается использование ограничений Enum. Смотрите мой более длинный ответ полностью внизу.   -  person baumgarb    schedule 10.05.2018
comment
NB: эта функция поддерживается начиная с C # 7.3.   -  person Yahoo Serious    schedule 15.06.2018
comment
Несмотря на то, что это работает в C # 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL , в результате чего во всей иерархии передаваемых дженериков необходимо также указать ограничение struct.   -  person That Marc    schedule 26.05.2019


Ответы (21)


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

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

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 (и, очевидно, ранее) не поддерживает добавление ссылок на netmodule, что означает, что вам придется оставить его в двух отдельных сборках при отладке. Единственный способ добавить их как часть сборки - это запустить 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

Его легче поддерживать, так как это хорошо известный язык с полной поддержкой IDE Visual Studio, но для него все равно нужен отдельный проект в вашем решении. Однако он, естественно, производит значительно другой 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);
person Christopher Currens    schedule 10.11.2011
comment
Да, очень хардкорно. Я очень уважаю того, кто умеет кодировать на IL, и знает, как функции поддерживаются на более высоком уровне языка - уровне, который многие из нас по-прежнему считают низким уровнем в приложениях, бизнес-правилах. , UI, библиотеки компонентов и т. Д. - person TonyG; 25.03.2012
comment
@ruslan - в первом абзаце ответа написано, что вы не можете этого сделать на C #. На самом деле это то, что показывает этот ответ: очень возможно в cil (поскольку приведенный выше код успешно работает при использовании на других языках .net), но невозможно в C # сам по себе. - person Christopher Currens; 23.04.2012
comment
Я действительно хотел бы знать, почему команда C # еще не разрешила это, поскольку это уже поддерживается MSIL. - person MgSam; 19.09.2012
comment
@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. - person Christopher Currens; 19.09.2012
comment
Работая со смесью C и Ассемблера (различных) в течение многих лет по необходимости, я могу только сказать, что я держусь подальше от этих неподдерживаемых вещей. Одна вещь, которую я никогда не понимал, - это ПОЧЕМУ команда .NET не поддерживает это, если у MSIL есть возможность сделать это, и так много людей просят об этом и вынуждены писать ужасные обходные пути. - person Lord of Scripts; 14.01.2013
comment
@LordofScripts: я думаю, причина в том, что, поскольку класс, который ограничивает T значением System.Enum, не сможет делать с T все, что люди могут ожидать, авторы C # решили, что они могут вообще запретить это. Я считаю это решение неудачным, поскольку C # просто проигнорировал любую специальную обработку System.Enum ограничений, было бы возможно написать метод расширения HasAnyFlags<T>(this T it, T other), который был бы на порядки быстрее, чем Enum.HasFlag(Enum) и который проверял бы типы своих аргументов. - person supercat; 12.04.2013
comment
@MichaelBlackburn Это сложнее, чем кажется, в основном из-за битовых флагов в перечислениях. Пользователь github по имени HaloFour дает хорошее резюме в этом выпуске Roslyn. - person Christopher Currens; 27.10.2015
comment
Только мои три цента .. Поскольку System.Enum слишком особенный, чтобы привлекать к себе внимание, и поскольку у нас уже есть where T:class, where T:struct и т. Д., where T:enum кажется довольно крутым и, вероятно, тривиальным для реализации .. - person quetzalcoatl; 01.08.2017
comment
Несмотря на то, что это работает в C # 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL , в результате чего во всей иерархии передаваемых дженериков необходимо также указать ограничение struct. Проклятие... - person That Marc; 26.05.2019
comment
Да, лучше сказать where T : struct, System.Enum, потому что тогда компилятор будет знать, что это всегда тип значения. Как только что сказал @ThatMarc, это необходимо, если вы хотите использовать T? (Nullable<T>). Это также может помочь вам в других ситуациях, например T t = …; if (t == null) { … }, когда компилятор выдаст полезное сообщение, если знает, что T является типом значения. - person Jeppe Stig Nielsen; 22.05.2020
comment
пожалуйста, я новичок в MSIL, куда мне вставить и запустить код MSIL (хочу посмотреть, как это работает!). Спасибо! - person Oluwadamilola Adegunwa; 04.10.2020

Поскольку Enum Type реализует интерфейс 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. Хотя шансы редки.

person Vivek    schedule 17.09.2008
comment
это похоже только на vs2008 и новее, не так ли? а может это просто не в vb2005? - person Maslow; 29.05.2009
comment
Обобщения доступны начиная с .NET 2.0. Следовательно, они также доступны в vb 2005. - person Vivek; 01.06.2009
comment
Что ж, сделайте его еще более ограниченным, если вы решите пойти по этому пути ... используйте класс TestClass ‹T›, где T: struct, IComparable, IFormattable, IConvertible - person Ricardo Nolde; 27.09.2010
comment
Другое предложение - определить универсальный тип с идентификатором TEnum. Таким образом: public TEnum GetEnumFromString ‹TEnum› (строковое значение), где TEnum: struct, IConvertible, IComparible, IFormattable {} - person Lisa; 24.11.2011
comment
Вы не выиграете от включения других интерфейсов, потому что почти все встроенные типы значений реализуют все эти интерфейсы. Это особенно верно для ограничений на общий метод расширения, который чрезвычайно удобен для работы с перечислениями, за исключением того факта, что эти методы расширения подобны вирусу, заражающему все ваши объекты. IConvertable, по крайней мере, немного сужает его. - person russbishop; 05.03.2014
comment
Конечно, этот принятый ответ отмечает, что лучшим исключением для генетических типов является NotSupportedExection(), а не ArgumentException(). - person John Alexiou; 07.11.2014
comment
не совсем получает проверку времени компиляции, которую я ищу - person Sam I am says Reinstate Monica; 13.02.2015
comment
@SamIam: Когда вы писали, этой теме было 6 с половиной лет, и вы были правы, ни один из ответов во время компиляции не проверял. Затем, всего через 3 дня, через 6 лет, ваше желание исполнилось - см. Сообщение Жюльена Лебоскена ниже. - person David I. McIntosh; 12.04.2015
comment
В самом деле, Дэвид, по прошествии времени и развития языка и совокупных знаний сообщества, теперь у нас есть лучшее решение благодаря Жюльену Лебоскуэну. Проголосовал за этот ответ не потому, что он плохой, а потому, что ответ Жюльена лучше и должен превосходить этот по голосам. Пожалуйста, сообщите мне, если это осуждают. - person Jelle Fresen; 16.12.2015
comment
@Lisa - в вашем примере есть орфографическая ошибка, из-за которой он не работает, я считаю, что это должно быть IComparable, а не IComparible (a not i) - person Scott; 14.03.2017
comment
для ядра .net используйте typeof (T) .GetTypeInfo (). IsEnum - person tchelidze; 06.04.2017
comment
@ Скотт. Ты совершенно прав. Но, похоже, я не могу отредактировать свой комментарий. Извините. - person Lisa; 10.04.2017
comment
Очень старая тема, но после C # 7.3 произошли огромные улучшения. Теперь полностью поддерживается использование ограничений Enum. Смотрите мой более длинный ответ полностью внизу. - person baumgarb; 10.05.2018
comment
Эта функция поддерживается начиная с C # 7.3. - person Yahoo Serious; 15.06.2018

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 и 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 и любым типом интерфейса.

person Julien Lebosquain    schedule 15.02.2015
comment
Кроме того, не могли бы вы немного уточнить свой ответ. Комментарий, который вы включаете из спецификации языка, говорит нам, что ваш тип Enum должен быть System.Object, System.FalueType, System.Enum или любым другим интерфейсом. Каким образом он ограничивается только типом System.Enum? Разве вам не нужно делать public class EnumUtils : EnumClassUtils<Enum> where Enum : struct, IConvertible? Благодарю. - person David I. McIntosh; 11.04.2015
comment
@ DavidI.McIntosh EnumClassUtils<System.Enum> достаточно, чтобы ограничить T любыми System.Enum и любыми производными типами. struct на Parse затем ограничивает его до реального типа перечисления. В какой-то момент вам нужно ограничить Enum. Для этого ваш класс должен быть вложенным. См. gist.github.com/MrJul/7da12f5f2d6c69f03d79. - person Julien Lebosquain; 11.04.2015
comment
Ах, теперь я понимаю, как это работает. Тогда использование класса всегда должно осуществляться посредством ссылки на него как на вложенный класс - неприятно, но я думаю, что это лучшее, на что можно надеяться. Кстати, спасибо за быстрый ответ. - person David I. McIntosh; 12.04.2015
comment
Чтобы быть ясным, мой неприятный комментарий не был комментарием к вашему решению - это действительно красивый хак. Просто неприятно, что MS заставляет нас использовать такой запутанный хак. - person David I. McIntosh; 12.04.2015
comment
Есть ли способ работать с этим, чтобы его можно было использовать для методов расширения? - person Mord Zuber; 29.04.2015
comment
@Max К сожалению, я так не думаю :( - person Julien Lebosquain; 06.05.2015
comment
Я бы добавил внутренний конструктор, чтобы предотвратить странное наследование этого класса. - person John Gietzen; 10.05.2015
comment
Было бы неплохо, если бы вы могли это сделать: где T: class, struct - person bubbleking; 07.08.2016
comment
Что здесь дает ограничение where TClass : class? - person tsemer; 04.01.2017
comment
Есть ли способ еще больше ограничить TEnum, чтобы int v; TEnum e = (TEnum) v; было разрешено? - person Marc L.; 14.07.2017
comment
Пора! И он ЛУЧШЕ поддерживает int v; TEnum e = (TEnum)v;, то есть лучше быть достаточно умным, чтобы понять, что все Enum - это целые числа! Не надоедай Microsoft! - person Triynko; 16.05.2018
comment
@Trinkyo enum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer } - person M.Stramm; 03.12.2018
comment
Несмотря на то, что это работает в C # 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL , в результате чего во всей иерархии передаваемых дженериков необходимо также указать ограничение struct. Проклятие... - person That Marc; 26.05.2019
comment
@tsemer: where TClass : class нужен, чтобы вы, например, не могли написать EnumUtils.Parse<int>("value");. - person Andre Kampling; 12.07.2021

Изменить

На этот вопрос великолепно ответил Жюльен Лебоскен. Я также хотел бы дополнить его ответ 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 value (введено в 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;
    }
}
person Yahoo Serious    schedule 24.05.2013

Вы можете определить статический конструктор для класса, который будет проверять, является ли тип 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) для преобразования из строки в перечисление. Последний истинный параметр предназначен для игнорирования регистра ввода.

person Karg    schedule 17.09.2008
comment
Это хороший вариант для общих классов, но, конечно, он не помогает для общих методов. - person McGarnagle; 09.02.2015
comment
Кроме того, это также не применяется во время компиляции, вы можете знать, что указали не Enum T только при выполнении конструктора. Хотя это намного лучше, чем ждать конструктора экземпляра. - person jrh; 09.10.2018

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

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

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

ИЗМЕНИТЬ

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

ИЗМЕНИТЬ 2

Теперь это в C # 7.3 вперед (примечания к выпуску < / а>)

Образец;

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;
}
person DiskJunky    schedule 22.03.2018

Также следует учитывать, что, поскольку выпуск 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 на момент написания этого РЕДАКТИРОВАНИЯ еще не поддерживает C # 7.3. После активации ReSharper он выделяет ограничение Enum как ошибку, сообщающую мне Невозможно использовать 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' в качестве ограничения параметра типа < / em>. ReSharper предлагает быстрое решение проблемы Удалить ограничение Enum для параметра типа T метода

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

person baumgarb    schedule 10.05.2018
comment
Какую версию VS вы используете? - person mshwf; 11.05.2018
comment
@MohamedElshawaf Я считаю, что это версия 15.7, которая поддерживает C # 7.3. - person Patrick Roberts; 11.05.2018
comment
Я думаю, что лучше написать where T : struct, Enum, чтобы не передавать сам System.Enum в качестве параметра типа. - person Mariusz Pawelski; 11.05.2018
comment
Как @MariuszPawelski, я пишу struct, Enum. Мое обоснование объясняется в ответе и комментариях здесь. - person Stephen Kennedy; 18.06.2018
comment
Информация ReSharper мне действительно помогла. Обратите внимание, что последняя предварительная версия поддерживает эту функцию. - person DalSoft; 06.11.2018
comment
Я удивлен, что это заняло так много времени - person Sam I am says Reinstate Monica; 01.05.2019

Я модифицировал образец 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);
    }
}
person Bivoauc    schedule 17.09.2008
comment
Я бы не вернул значение по умолчанию в случае неудачи; Я бы позволил исключению распространяться (как и в случае с Enum.Parse). Вместо этого используйте TryParse, возвращающий логическое значение, и возвращайте результат с помощью параметра out. - person Mark Simpson; 26.06.2010
comment
OP хочет, чтобы регистр регистрировался, но это не так. - person Konrad Morawski; 04.04.2012

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

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;
}
person Martin    schedule 16.12.2010
comment
Это лучше, чем принятый ответ, потому что он позволяет вам вызывать defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo), даже если вы не знаете, какой это тип перечисления, только то, что объект является перечислением. - person styfle; 02.11.2017
comment
Однако предварительная проверка с IsDefined испортит нечувствительность к регистру. В отличие от Parse, IsDefined не имеет аргумента ignoreCase, и MSDN сообщает, что он соответствует только точному case. - person Nyerguds; 06.03.2018

У меня есть особое требование, когда мне нужно использовать 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());
}
person Sunny Rajwadi    schedule 18.04.2010

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

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;
      }
}
person dimarzionist    schedule 17.09.2008
comment
Если вам нужна нечувствительность к регистру, просто замените return (TValue)Enum.Parse(typeof (TValue), value); на return (TValue)Enum.Parse(typeof (TValue), value, true); - person Paulo Santos; 28.01.2010

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

Цитировать:

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

Кто знает

person Andrew Backer    schedule 07.07.2009
comment
Управляемые расширения для C ++ не имеют НИКАКОЙ поддержки для дженериков, я думаю, вы имеете в виду C ++ / CLI. - person Ben Voigt; 27.03.2011

Это мой взгляд на это. В сочетании с ответами и 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

person KarmaEDV    schedule 25.03.2014
comment
В этом нет никакого смысла. Если TEnum на самом деле является типом Enum, но text является пустой строкой, тогда вы получите ArgumentException, в котором говорится, что TEnum должен быть типом Enum, даже если это так. - person Nick; 28.08.2014

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

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>();
}
person Jeff    schedule 04.10.2010

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

Однако обратите внимание, что вы не можете использовать общее ограничение, такое как where T : 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;
}
person expert    schedule 23.04.2012

Я инкапсулировал решение Vivek в служебный класс, который вы можете использовать повторно. Обратите внимание, что вы все равно должны определить ограничения типа «where 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);
        }
    }
}
person niaher    schedule 25.07.2013

Я создал расширение. Метод 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
person Basheer AL-MOMANI    schedule 01.11.2016
comment
Хотя это, вероятно, работает, это почти не имеет отношения к вопросу. - person quetzalcoatl; 01.08.2017

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

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

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

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

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

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

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

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

person BatteryBackupUnit    schedule 24.07.2017

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

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;
        }
    }
person Cubelaster    schedule 06.11.2019

Если после этого можно использовать прямое приведение, я думаю, вы можете использовать базовый класс 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);
person uluorta    schedule 23.11.2017
comment
использование Enum.ToObject() даст более гибкий результат. Кроме того, вы можете выполнять сравнения строк без учета регистра, что устраняет необходимость вызова ToLower() - person DiskJunky; 28.03.2018

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

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

Вместо того, чтобы принимать только строковое значение, примите 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;
    }


}
person Rodney P. Barbati    schedule 30.05.2018