Створіть загальний метод, що обмежує T до Enum


1187

Я будую функцію, щоб розширити цю 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;
    }
}

EDIT: (16 лютого 2015 р.) Джульєн Лебосквайн нещодавно опублікував універсальне рішення , захищене від компіляції, у форматі MSIL або F # нижче, що варто переглянути і отримати репутацію. Я видалю це редагування, якщо розчин попливе далі на сторінку.


10
Можливо, вам слід скористатися ToUpperInvariant () замість ToLower () ...
Макс Галкін

31
@Shimmy: Щойно ви передаєте тип значення методу розширення, ви працюєте над його копією, тому ви не можете змінити його стан.
Гаро Єріязарян

4
Знайте, що це стара нитка, не знаю, чи змінили вони речі, але методи розширення добре спрацьовують для типів значень, впевнені, що вони не завжди можуть мати стільки сенсу, але я використовував "public statique TimeSpan Seconds (це int x) { повернути TimeSpan.FromSeconds (x);} ", щоб увімкнути синтаксис" Зачекайте. Для (5.Seconds ()) ... "
Jens

6
Зрозумійте, це не було частиною питання, але ви можете покращити логіку циклу передбачень, використовуючи String.Equals з StringComppare.InvariantCultureIgnoreCase
Firestrand

Відповіді:


1005

Оскільки інтерфейс 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.
Vivek

46
Ну, зробіть це ще більш обмеженим, тоді, якщо ви вирішите піти цим шляхом ... використовуйте "клас TestClass <T> де T: struct, IComparable, IFormattable, IConvertible"
Рікардо

106
Ще одна пропозиція - визначити загальний тип за допомогою ідентифікатора TEnum. Таким чином: public TEnum GetEnumFromString <TEnum> (рядок значення), де TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
Ви не заробляєте багато, включаючи інші інтерфейси, оскільки майже всі вбудовані типи значень реалізують усі ці інтерфейси. Особливо це стосується обмежень у загальному методі розширення, що надзвичайно зручно для роботи над перерахунками, за винятком того, що ці методи розширення - це як вірус, який заражає всі ваші об’єкти. IConvertable принаймні звужує його зовсім небагато.
russbishop

2
@SamIam: Коли ви розмістили повідомлення, ця тема була то, що їй було 6 з половиною років, і ви були правильні, жоден час перевірки не збирався в жодній із відповідей. Тоді лише через 3 дні, через 6 років, ви отримали своє бажання - дивіться пост Жульєна Лебосквайна нижче.
Девід І. Макінтош

662

Ця функція, нарешті, підтримується в 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);

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)і який тип перевіряв його аргументи.
supercat

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і 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>достатньо для обмеження Т будь-яким System.Enumта будь-яким похідним типом. structна Parseте , обмежує його далі до реального перечислимого типу. Вам потрібно обмежитися Enumв якийсь момент. Для цього ваш клас повинен бути вкладений. Дивіться gist.github.com/MrJul/7da12f5f2d6c69f03d79
Жульєн Лебосквайн

7
Щоб було зрозуміло, мій коментар "не приємно" не був коментарем до вашого рішення - це справді гарний злом. Просто "не приємно", що MS змушує нас використовувати такий перекручений хак.
Девід І. Макінтош

2
Чи є спосіб, щоб це було також корисно для методів розширення?
Морд Зубер

3
Що where TClass : classтут набуває обмеження?
цемер

2
@Trinkyo enum 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);

Старий

Мої старі удосконалення на Vivek, використовуючи коментарі та "нові" розробки:

  • використовувати 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;
    }
}

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) для перетворення з рядка в enum. Останній вірний параметр - це ігнорування випадку вводу.


1
Це хороший варіант для загальних занять - але, звичайно, це не допомагає для загальних методів.
McGarnagle

Крім того, це також не виконується під час компіляції, ви могли б знати, що ви надали не, 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, перегляньте наступний знімок екрана: введіть тут опис зображення

EDIT 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 або вище.


1
Яку версію VS ви використовуєте?
mshwf

1
@MohamedElshawaf Я вважаю, що версія 15.7 містить підтримку C # 7.3
Патрік Робертс

1
Я думаю, що краще написати where T : struct, Enum, щоб не переходити System.Enumяк параметр типу.
Маріуш Павельський

Як @MariuszPawelski пишу struct, Enum. Моє обґрунтування пояснюється у відповіді та коментарях тут .
Стівен Кеннеді

Інформація про ReSharper мені дуже допомогла. Зверніть увагу, що остання версія попереднього перегляду підтримує цю функцію.
DalSoft

11

Я модифікував зразок димарзіоністом. Ця версія працюватиме лише з 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);
    }
}

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

1
Якщо вам потрібна нечутливість до справи, просто замініть return (TValue)Enum.Parse(typeof (TValue), value);наreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Пауло Сантос

3

Цікаво, що, мабуть, це можливо і в інших мовах (керований 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


2
Це насправді не має сенсу. Якщо TEnumнасправді це тип Enum, але textце порожня рядок, то ви отримуєте ArgumentExceptionприказку "TEnum повинен бути типом Enum", навіть якщо це так.
Нік

3

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

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

На момент написання, на зустрічах з розробки мови функція "В дискусії".

EDIT

Відповідно до інформації nawfal , це вводиться в C # 7.3 .


1
Цікава дискусія там, дякую. Ще нічого не встановлено в камені (поки що)
John

1
@johnc, дуже вірно , але варто записка і це часто задається особливість. Справедливі шанси на це приходять.
DiskJunky

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

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

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

1

Я створив розширення Method. 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

1

Як зазначено в інших відповідях раніше; хоча це не може бути виражено у вихідному коді, це фактично можна зробити на рівні IL. @Christopher Currens відповідь показує , як IL зробити для цього.

За допомогою додатка Fody s ExtraConstraints.Fody для цього досягти дуже простого способу, в комплекті з інструментами побудови. Просто додайте їхні пакунки з назви ( Fody, ExtraConstraints.Fody) до свого проекту та додайте обмеження наступним чином (Уривок з Readme ExtraConstraints):

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

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

і Фоді додасть необхідний ІЛ для наявності обмеження. Також зверніть увагу на додаткову функцію обмеження делегатів:

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

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

Щодо Enums, ви можете також взяти до уваги надзвичайно цікавий Enums.NET .


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

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

використання Enum.ToObject()дало б більш гнучкий результат. Додано до цього, ви можете робити порівняння рядків без чутливості до регістру, що ToLower()
нівелює

-6

Далі для повноти наступне рішення Java. Я впевнений, що те ж саме можна зробити і в C #. Уникає необхідності вказувати тип де-небудь у коді - замість цього ви вказуєте його у рядках, які ви намагаєтеся розібрати.

Проблема полягає в тому, що немає жодного способу дізнатися, з яким перерахуванням може відповідати рядок - тому відповідь має вирішити цю проблему.

Замість того, щоб приймати лише рядкове значення, прийміть рядок, який має і перерахування, і значення у формі "enumeration.value". Робочий код наведено нижче - потрібна Java 1.8 або новіша версія. Це також зробить XML більш точним, оскільки ви побачите щось на зразок color = "Color.red", а не просто color = "red".

Ви б назвали метод acceptEnumeratedValue () з рядком, що містить ім'я значення пункту enum name.

Метод повертає формальне перелічене значення.

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


}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.