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

1005

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

EDIT Все предложения, приведенные ниже, были очень благодарны, спасибо.

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

  • 9
    Может быть, вы должны использовать ToUpperInvariant () вместо ToLower () ...
  • 0
    Почему методы расширения только для ссылочных типов?
Показать ещё 16 комментариев
Теги:
generics
enums

23 ответа

905
Лучший ответ

Так как 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. Однако шансы редки.

  • 0
    это кажется только vs2008 и новее, верно? или может это просто не в vb2005?
  • 2
    Обобщения доступны с .NET 2.0. Следовательно, они доступны и в vb 2005.
Показать ещё 13 комментариев
485

Эта функция, наконец, поддерживается в С# 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;
}

Обязательно установите версию своего языка в проекте С# до версии 7.3.


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

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

Что генерирует функцию, которая будет выглядеть так, если она была действительной С#:

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

Затем со следующим кодом С#:

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 вместо С#, и единственным добавленным преимуществом является то, что вы можете ограничить этот метод System.Enum. Это также своего рода облом, потому что он скомпилирован в отдельную сборку. Однако это не значит, что вы должны развернуть его таким образом.

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

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

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

К сожалению, VS2010 (и раньше, очевидно) не поддерживает добавление ссылок netmodule, что означает, что вам придется оставить его в двух отдельных сборках при отладке. Единственный способ добавить их как часть вашей сборки - это запустить сам csc.exe, используя аргумент командной строки /addmodule:{files}. Это не было бы слишком болезненно в MSBuild script. Конечно, если вы храбры или глупы, вы можете запускать 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, но для этого вам по-прежнему нужен отдельный проект. Однако он, естественно, производит значительно разные ИЛ (код очень отличается), и он полагается на библиотеку 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);
  • 60
    Да, очень хардкор. Я очень уважаю того, кто умеет кодировать на IL и знает, как эти функции поддерживаются на более высоком уровне языка - уровне, который многие из нас все еще считают низким на уровне приложений, бизнес-правил, пользовательского интерфейса, библиотек компонентов и т. Д. ,
  • 2
    @ruslan - он говорит, что вы не можете сделать это в c # в первом абзаце ответа. Вот что на самом деле показывает этот ответ: очень возможно в cil (поскольку приведенный выше код успешно работает при использовании в других языках .net), однако в C # само по себе это невозможно.
Показать ещё 8 комментариев
159

С# ≥ 7.3

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

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

С# ≤ 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");

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

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

  • 0
    Кроме того, можете ли вы уточнить ваш ответ немного. Комментарий, который вы включаете из спецификации языка, говорит нам, что ваш тип Enum должен быть System.Object, System.FalueType, System.Enum или любым интерфейсом. Как это далее ограничено только типом System.Enum? Разве вам не нужно делать public class EnumUtils : EnumClassUtils<Enum> where Enum : struct, IConvertible ? Благодарю.
  • 7
    @ DavidI.McIntosh EnumClassUtils<System.Enum> достаточно, чтобы ограничить T любым System.Enum и любыми производными типами. struct на Parse затем ограничивает его до реального типа enum. Вы должны ограничить Enum в какой-то момент. Для этого ваш класс должен быть вложенным. См. Gist.github.com/MrJul/7da12f5f2d6c69f03d79
Показать ещё 11 комментариев
29

Edit

На вопрос теперь великолепно ответил Жюльен Лебоскейн. Я также хотел бы продлить его ответ 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 answer, используя комментарии и "новые" разработки:

  • используйте TEnum для ясности для пользователей.
  • добавить дополнительные интерфейсные ограничения для дополнительной проверки ограничений
  • let 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 через С#.

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

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

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

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

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

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

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

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

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

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

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

  • 1
    Какую версию VS вы используете?
  • 1
    Это должен быть принятый ответ сейчас
Показать ещё 4 комментария
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
    ОП хочет, чтобы он не учитывал регистр, это не так.
Показать ещё 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) даже если вы не знаете, какой это тип перечисления, только то, что объект является перечислением ,
  • 1
    Однако предварительная проверка с помощью IsDefined разрушит нечувствительность к регистру. В отличие от Parse , IsDefined не имеет аргумента ignoreCase , а MSDN говорит, что он соответствует только точному ignoreCase .
5

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

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

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

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

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

ИЗМЕНИТЬ

Согласно nawfal, это вводится в С# 7.3.

  • 1
    Интересная дискуссия там, спасибо. Ничего не установлено в камне, хотя (пока)
  • 1
    @johnc, очень верно , но стоит отметка , и это часто задаваемый особенность. Справедливые шансы на это поступает.
Показать ещё 1 комментарий
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

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

Цитата:

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

Кто знает

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

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

С Fody Надстройка ExtraConstraints.Fody там очень простой способ, в комплекте со сборкой, для достижения этого. Просто добавьте свои пакеты nuget (Fody, ExtraConstraints.Fody) в свой проект и добавьте ограничения следующим образом: (Excerpt of 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.

1

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

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

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

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
  • 0
    Хотя это, вероятно, работает, это почти не имеет отношения к вопросу.
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

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

Обратите внимание, что вы не можете использовать общее ограничение, например 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

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

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

У меня есть еще одно решение для этого.

private T GetEnum<T>(string str, T defaultValue)
{
    T outPutValue = defaultValue;
    outPutValue = (T)Enum.Parse(typeof(T), str, true);
    return outPutValue;
}
0

Я написал это как часть класса настроек.

public T GetEnum<T>(string setting, T defaultValue) where T : struct
{
  var res = GetString(setting);

  if (res == string.Empty)
    return defaultValue;

  var parse = (T)Enum.Parse(typeof(T), res);
  return parse;
}
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);
  • 0
    использование Enum.ToObject() даст более гибкий результат. Кроме того, вы можете выполнять сравнения строк без учета регистра, что ToLower() необходимость вызова ToLower()
-3

Вы должны сделать .trim(), когда вы проверите значение для isNullOrEmpty, а также переместите .trim().tolower() из цикла, чтобы каждый раз вы не выполняли одну и ту же логику.

-6

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

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

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


}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню