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

Почему методы расширения используются только для ссылочных типов?

Shimmy Weitzhandler 18.08.2009 16:08

@Shimmy: как только вы передаете тип значения методу расширения, вы работаете над его копией, поэтому вы не можете изменить его состояние.

Garo Yeriazarian 26.06.2010 01:52

Знайте, что это старый поток, не знаю, изменили ли они что-то, но методы расширения отлично работают для типов значений, конечно, они не всегда могут иметь такой смысл, но я использовал «общедоступные статические секунды TimeSpan (this int x) { return TimeSpan.FromSeconds (x);} "для включения синтаксиса" Wait.For (5.Seconds ()) ... "

Jens 06.03.2012 00:01

Может быть, вы следует использовать ToUpperInvariant () вместо ToLower () ...

Max Galkin 19.09.2008 17:07

Поймите, это не было частью вопроса, но вы можете улучшить логику цикла foreach, используя String.Equals с StringComparison.InvariantCultureIgnoreCase

Firestrand 02.11.2012 19:01

Зачем использовать этот foreach-loop? Enum.Parse содержит параметр ignoreCase (я думаю, с .Net 2.0).

Yahoo Serious 23.05.2013 19:08

Я пошел дальше и добавил отвечать с существующим параметром ignoreCase и общим значением default в качестве дополнительных аргументов, а также некоторые другие улучшения, предложенные другими.

Yahoo Serious 24.05.2013 18:10

Стоит отметить, что Enum.Parse может обрабатывать перечисление с атрибутом [Flags], при условии, что значения в строке разделяются запятыми.

yoyo 22.11.2013 23:49

Опубликуйте свое решение как ответный пост. Не включайте ответы в вопросы.

BartoszKP 29.09.2015 00:07

Как создается строка значения? Если это было создано с использованием метода Enum.ToString(), а тип Enum отмечен атрибутом [Flags], defaultValue всегда будет возвращаться из метода с value = (enum.type1 | enum.type2).ToString() == type1, type2. Угловой шкаф.

paxmemento 16.10.2015 22:24

проверьте этот ответ, написанный мной stackoverflow.com/a/38410351/4009642

bigworld12 18.07.2016 04:23

@ bigworld12 Изначальное требование, которое мне нужно было сделать, давно потеряно в глубине веков, но, тем не менее, это очень комплексное решение :)

johnc 25.07.2016 05:20

Реализация этой функции находится на чертежной доске для C# 7! Проголосуйте! github.com/dotnet/roslyn/issues/262

Airn5475 13.09.2016 16:09

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

baumgarb 10.05.2018 12:56

NB: эта функция поддерживается начиная с C# 7.3.

Yahoo Serious 15.06.2018 12:44

Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также.

That Marc 26.05.2019 18:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 261
17
386 361
21
Перейти к ответу Данный вопрос помечен как решенный

Ответы 21

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

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;
      }
}

Если вам нужна нечувствительность к регистру, просто замените return (TValue)Enum.Parse(typeof (TValue), value); на return (TValue)Enum.Parse(typeof (TValue), value, true);.

Paulo Santos 28.01.2010 13:04

Я модифицировал образец 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);
    }
}

Я бы не вернул значение по умолчанию в случае неудачи; Я бы позволил исключению распространяться (как и в случае с Enum.Parse). Вместо этого используйте TryParse, возвращающий логическое значение, и возвращайте результат с помощью параметра out.

Mark Simpson 26.06.2010 01:59

OP хочет, чтобы регистр регистрировался, но это не так.

Konrad Morawski 04.04.2012 10:46

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

Это хороший вариант для общих классов, но, конечно, он не помогает для общих методов.

McGarnagle 09.02.2015 22:18

Кроме того, это также не применяется во время компиляции, вы должны знать, что предоставили не EnumT при выполнении конструктора. Хотя это намного лучше, чем ждать конструктора экземпляра.

jrh 09.10.2018 15:42
Ответ принят как подходящий

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

это похоже только на vs2008 и новее, не так ли? а может это просто не в vb2005?

Maslow 29.05.2009 22:28

Обобщения доступны начиная с .NET 2.0. Следовательно, они также доступны в vb 2005.

Vivek 01.06.2009 21:07

Что ж, сделайте его еще более ограниченным, если вы решите пойти по этому пути ... используйте "class TestClass <T>, где T: struct, IComparable, IFormattable, IConvertible"

Ricardo Nolde 27.09.2010 22:42

Другое предложение - определить универсальный тип с идентификатором TEnum. Таким образом: public TEnum GetEnumFromString <TEnum> (строковое значение), где TEnum: struct, IConvertible, IComparible, IFormattable {}

Lisa 24.11.2011 10:52

Вы не получите многого, включив другие интерфейсы, потому что почти все встроенные типы значений реализуют все эти интерфейсы. Это особенно верно для ограничений на общий метод расширения, который чрезвычайно удобен для работы с перечислениями, за исключением того факта, что эти методы расширения подобны вирусу, заражающему все ваши объекты. IConvertable, по крайней мере, немного сужает его.

russbishop 05.03.2014 22:04

Конечно, принятый ответ это отмечает, что лучшим исключением для генетических типов является NotSupportedExection(), а не ArgumentException().

John Alexiou 07.11.2014 17:59

не совсем получает проверку времени компиляции, которую я ищу

Sam I am says Reinstate Monica 13.02.2015 01:05

@SamIam: Когда вы писали, этой теме было 6 с половиной лет, и вы были правы, ни один из ответов во время компиляции не проверял. Затем, всего через 3 дня, через 6 лет, ваше желание исполнилось - см. Сообщение Жюльена Лебоскена ниже.

David I. McIntosh 12.04.2015 04:55

В самом деле, Дэвид, по прошествии времени и развития как языка, так и совокупных знаний сообщества, теперь у нас есть лучшее решение благодаря Жюльену Лебоскуэну. Проголосовал за этот ответ не потому, что он плохой, а потому, что ответ Жюльена лучше и должен превосходить этот по голосам. Пожалуйста, сообщите мне, если это осуждают.

Jelle Fresen 16.12.2015 11:49

@Lisa - в вашем примере есть орфографическая ошибка, из-за которой он не работает, я считаю, что это должно быть IComparable, а не IComparible (a not i)

Scott 14.03.2017 18:44

для ядра .net используйте typeof (T) .GetTypeInfo (). IsEnum

tchelidze 06.04.2017 09:24

@ Скотт. Ты совершенно прав. Но, похоже, я не могу отредактировать свой комментарий. Извини.

Lisa 10.04.2017 09:10

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

baumgarb 10.05.2018 12:56

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

Yahoo Serious 15.06.2018 12:44

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

Цитировать:

... Both constraints actually produce valid IL and can also be consumed by C# if written in another language (you can declare those constraints in managed C++ or in IL).

Кто знает

Управляемые расширения для C++ не имеют НИКАКОЙ поддержки для дженериков, я думаю, вы имеете в виду C++ / CLI.

Ben Voigt 27.03.2011 07:30

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

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

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>();
}

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

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;
}

Это лучше, чем принятый ответ, потому что он позволяет вам вызывать defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo), даже если вы не знаете, какой это тип перечисления, только то, что объект является перечислением.

styfle 02.11.2017 18:55

Однако предварительная проверка с помощью IsDefined испортит нечувствительность к регистру. В отличие от Parse, IsDefined не имеет аргумента ignoreCase, и MSDN говорит, что он соответствует только точному регистру.

Nyerguds 06.03.2018 18:03

Эта функция наконец-то поддерживается в 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 (и, очевидно, ранее) не поддерживает добавление ссылок на 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);

Да, очень хардкорно. Я очень уважаю тех, кто умеет кодировать на IL, и знает, как функции поддерживаются на более высоком уровне языка - уровне, который многие из нас по-прежнему считают низким уровнем в приложениях, бизнес-правилах, пользовательском интерфейсе, библиотеках компонентов и т. д.

TonyG 25.03.2012 05:31

@ruslan - в первом абзаце ответа написано, что вы не можете этого сделать на C#. На самом деле это то, что показывает этот ответ: очень возможно в cil (поскольку приведенный выше код успешно работает при использовании на других языках .net), но невозможно в C# сам по себе.

Christopher Currens 23.04.2012 11:09

Я действительно хотел бы знать, почему команда C# еще не разрешила это, поскольку это уже поддерживается MSIL.

MgSam 19.09.2012 04:30

@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.

Christopher Currens 19.09.2012 04:58

Работая со смесью C и Ассемблера (различных) в течение многих лет по необходимости, я могу только сказать, что я держусь подальше от этих неподдерживаемых вещей. Одна вещь, которую я никогда не понимал, - это ПОЧЕМУ команда .NET не поддерживает это, если у MSIL есть возможность сделать это, и так много людей просят об этом и вынуждены писать ужасные обходные пути.

Lord of Scripts 14.01.2013 20:30

@LordofScripts: я думаю, причина в том, что, поскольку класс, который ограничивает T до System.Enum, не сможет делать с T все, что люди могут ожидать, авторы C# решили, что они могут вообще запретить это. Я считаю это решение неудачным, поскольку C# просто проигнорировал любую специальную обработку ограничений System.Enum, можно было бы написать метод расширения HasAnyFlags<T>(this T it, T other), который был бы на порядки быстрее, чем Enum.HasFlag(Enum), и который проверял бы тип своих аргументов.

supercat 12.04.2013 20:27

@MichaelBlackburn Это сложнее, чем кажется, в основном из-за битовых флагов в перечислениях. Пользователь github по имени HaloFour дает хорошее резюме в этот выпуск Roslyn.

Christopher Currens 27.10.2015 00:22

Только мои три цента .. Поскольку System.Enum слишком особенный, чтобы привлекать к себе внимание, и поскольку у нас уже есть where T:class, where T:struct и т. д., То where T:enum кажется довольно крутым и, вероятно, тривиальным для реализации ..

quetzalcoatl 01.08.2017 03:07

Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также. Проклятие...

That Marc 26.05.2019 18:54

Да, лучше сказать where T : struct, System.Enum, потому что тогда компилятор будет знать, что это всегда тип значения. Как только что сказал @ThatMarc, это необходимо, если вы хотите использовать T? (Nullable<T>). Это также может помочь вам в других ситуациях, таких как T t = …; if (t == null) { … }, когда компилятор выдаст полезное сообщение, если он знает, что T является типом значения.

Jeppe Stig Nielsen 22.05.2020 10:39

пожалуйста, я новичок в MSIL, куда мне вставить и запустить код MSIL (хочу посмотреть, как это работает!). Спасибо!

Damilola Adegunwa 04.10.2020 10:43

Мне понравилось решение Кристофера Карренса с использованием 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;
}

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

На вопрос теперь великолепно ответил Жюльен Лебоскен. Я также хотел бы расширить его ответ с помощью 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;
    }
}

Я инкапсулировал решение 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);
        }
    }
}

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

В этом нет никакого смысла. Если TEnum на самом деле является типом Enum, а text - пустой строкой, тогда вы получите ArgumentException, в котором говорится, что «TEnum должен быть типом Enum», даже если это так.

Nick 28.08.2014 23:17

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:

If type parameter S depends on type parameter T then: [...] It is valid for S to have the value type constraint and T to have the reference type constraint. Effectively this limits T to the types System.Object, System.ValueType, System.Enum, and any interface type.

Кроме того, не могли бы вы немного уточнить свой ответ. Комментарий, который вы включаете из спецификации языка, говорит нам, что ваш тип Enum должен быть System.Object, System.FalueType, System.Enum или любым другим интерфейсом. Каким образом он ограничивается только типом System.Enum? Разве вам не нужно делать public class EnumUtils : EnumClassUtils<Enum> where Enum : struct, IConvertible? Благодарю.

David I. McIntosh 11.04.2015 22:20

@ DavidI.McIntosh EnumClassUtils<System.Enum> достаточно, чтобы ограничить T любым System.Enum и любыми производными типами. struct на Parse затем ограничивает его реальным типом перечисления. В какой-то момент вам нужно ограничиться Enum. Для этого ваш класс должен быть вложенным. См. gist.github.com/MrJul/7da12f5f2d6c69f03d79

Julien Lebosquain 11.04.2015 23:51

Ах, теперь я понимаю, как это работает. Тогда использование класса всегда должно осуществляться посредством ссылки на него как на вложенный класс - неприятно, но я думаю, что это лучшее, на что можно надеяться. Кстати, спасибо за быстрый ответ.

David I. McIntosh 12.04.2015 04:00

Чтобы быть ясным, мой комментарий «неприятный» не был комментарием к вашему решению - это действительно красивый хак. Просто «неприятно», что MS заставляет нас использовать такой запутанный хак.

David I. McIntosh 12.04.2015 18:08

Есть ли способ работать с этим, чтобы его можно было использовать для методов расширения?

Mord Zuber 29.04.2015 18:11

@Max К сожалению, я так не думаю :(

Julien Lebosquain 06.05.2015 16:07

Я бы добавил внутренний конструктор, чтобы предотвратить странное наследование этого класса.

John Gietzen 10.05.2015 01:01

Было бы неплохо, если бы вы могли это сделать: где T: class, struct

bubbleking 07.08.2016 23:03

Что здесь дает ограничение where TClass : class?

tsemer 04.01.2017 19:50

Есть ли способ еще больше ограничить TEnum, чтобы разрешить int v; TEnum e = (TEnum) v;?

Marc L. 14.07.2017 15:30

Пора! И он ЛУЧШЕ поддерживает int v; TEnum e = (TEnum)v;, то есть лучше быть достаточно умным, чтобы понять, что все Enum - это целые числа! Не надоедай Microsoft!

Triynko 16.05.2018 00:56

@Trinkyo enum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int }enum AlsoNotAnInt : long { Well, Bummer }

M.Stramm 03.12.2018 15:00

Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также. Проклятие...

That Marc 26.05.2019 18:56

Я создал метод расширения 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

Хотя это, вероятно, работает, это почти не имеет отношения к вопросу.

quetzalcoatl 01.08.2017 03:13

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

С помощью надстройки Fodys 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.

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

использование Enum.ToObject() дало бы более гибкий результат. Кроме того, вы можете сравнивать строки без учета регистра, что устраняет необходимость вызова ToLower().

DiskJunky 28.03.2018 00:17

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

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

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

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

Согласно информации науфал, это вводится в 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;
}

Интересное обсуждение, спасибо. Пока еще ничего не высечено в камне (пока)

johnc 27.03.2018 23:59

@johnc, очень верно, но стоит отметить, что является - часто задаваемая функция. Хорошие шансы на его появление.

DiskJunky 28.03.2018 00:00

Это будет в C# 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/…. :)

nawfal 11.04.2018 20:46

Пожалуйста, проголосуйте за этот ответ, теперь, когда функция существует, он должен быть намного выше в списке! :)

Pac0 09.06.2020 15:59

Также следует учитывать, что, поскольку выпуск 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 в качестве ограничения параметра типа. ReSharper предлагает как быстрое исправление Удалить ограничение Enum для параметра типа T метода

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

Какую версию VS вы используете?

mshwf 11.05.2018 14:10

@MohamedElshawaf Я считаю, что это версия 15.7, которая поддерживает C# 7.3.

Patrick Roberts 11.05.2018 18:37

Я думаю, что лучше написать where T : struct, Enum, чтобы не передавать сам System.Enum в качестве параметра типа.

Mariusz Pawelski 11.05.2018 21:37

Как @MariuszPawelski, я пишу struct, Enum. Мое обоснование объясняется в ответе и комментариях здесь.

Stephen Kennedy 18.06.2018 21:52

Информация ReSharper мне очень помогла. Обратите внимание, что последняя предварительная версия поддерживает эту функцию.

DalSoft 06.11.2018 19:22

Я удивлен, что это заняло так много времени

Sam I am says Reinstate Monica 01.05.2019 01:26

Для полноты картины ниже представлено решение для 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;
    }


}

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

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;
        }
    }

Другие вопросы по теме