Я создаю функцию, расширяющую концепцию Enum.Parse, которая
Итак, я написал следующее:
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: как только вы передаете тип значения методу расширения, вы работаете над его копией, поэтому вы не можете изменить его состояние.
Знайте, что это старый поток, не знаю, изменили ли они что-то, но методы расширения отлично работают для типов значений, конечно, они не всегда могут иметь такой смысл, но я использовал «общедоступные статические секунды TimeSpan (this int x) { return TimeSpan.FromSeconds (x);} "для включения синтаксиса" Wait.For (5.Seconds ()) ... "
Может быть, вы следует использовать ToUpperInvariant () вместо ToLower () ...
Поймите, это не было частью вопроса, но вы можете улучшить логику цикла foreach, используя String.Equals с StringComparison.InvariantCultureIgnoreCase
возможный дубликат Кто-нибудь знает хороший способ обхода отсутствия общего ограничения enum?
Зачем использовать этот foreach-loop? Enum.Parse содержит параметр ignoreCase (я думаю, с .Net 2.0).
Я пошел дальше и добавил отвечать с существующим параметром ignoreCase и общим значением default в качестве дополнительных аргументов, а также некоторые другие улучшения, предложенные другими.
Стоит отметить, что Enum.Parse может обрабатывать перечисление с атрибутом [Flags], при условии, что значения в строке разделяются запятыми.
Опубликуйте свое решение как ответный пост. Не включайте ответы в вопросы.
Как создается строка значения? Если это было создано с использованием метода Enum.ToString(), а тип Enum отмечен атрибутом [Flags], defaultValue всегда будет возвращаться из метода с value = (enum.type1 | enum.type2).ToString() == type1, type2. Угловой шкаф.
проверьте этот ответ, написанный мной stackoverflow.com/a/38410351/4009642
@ bigworld12 Изначальное требование, которое мне нужно было сделать, давно потеряно в глубине веков, но, тем не менее, это очень комплексное решение :)
Реализация этой функции находится на чертежной доске для C# 7! Проголосуйте! github.com/dotnet/roslyn/issues/262
Очень старая тема, но после C# 7.3 произошли огромные улучшения. Теперь полностью поддерживается использование ограничений Enum. Смотрите мой более длинный ответ полностью внизу.
NB: эта функция поддерживается начиная с C# 7.3.
Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также.





Надеюсь, это будет полезно:
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);.
Я модифицировал образец 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.
OP хочет, чтобы регистр регистрировался, но это не так.
Вы можете определить статический конструктор для класса, который будет проверять, является ли тип 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) для преобразования из строки в перечисление. Последний истинный параметр предназначен для игнорирования регистра ввода.
Это хороший вариант для общих классов, но, конечно, он не помогает для общих методов.
Кроме того, это также не применяется во время компиляции, вы должны знать, что предоставили не EnumT при выполнении конструктора. Хотя это намного лучше, чем ждать конструктора экземпляра.
Поскольку тип 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?
Обобщения доступны начиная с .NET 2.0. Следовательно, они также доступны в vb 2005.
Что ж, сделайте его еще более ограниченным, если вы решите пойти по этому пути ... используйте "class TestClass <T>, где T: struct, IComparable, IFormattable, IConvertible"
Другое предложение - определить универсальный тип с идентификатором TEnum. Таким образом: public TEnum GetEnumFromString <TEnum> (строковое значение), где TEnum: struct, IConvertible, IComparible, IFormattable {}
Вы не получите многого, включив другие интерфейсы, потому что почти все встроенные типы значений реализуют все эти интерфейсы. Это особенно верно для ограничений на общий метод расширения, который чрезвычайно удобен для работы с перечислениями, за исключением того факта, что эти методы расширения подобны вирусу, заражающему все ваши объекты. IConvertable, по крайней мере, немного сужает его.
Конечно, принятый ответ это отмечает, что лучшим исключением для генетических типов является NotSupportedExection(), а не ArgumentException().
не совсем получает проверку времени компиляции, которую я ищу
@SamIam: Когда вы писали, этой теме было 6 с половиной лет, и вы были правы, ни один из ответов во время компиляции не проверял. Затем, всего через 3 дня, через 6 лет, ваше желание исполнилось - см. Сообщение Жюльена Лебоскена ниже.
В самом деле, Дэвид, по прошествии времени и развития как языка, так и совокупных знаний сообщества, теперь у нас есть лучшее решение благодаря Жюльену Лебоскуэну. Проголосовал за этот ответ не потому, что он плохой, а потому, что ответ Жюльена лучше и должен превосходить этот по голосам. Пожалуйста, сообщите мне, если это осуждают.
@Lisa - в вашем примере есть орфографическая ошибка, из-за которой он не работает, я считаю, что это должно быть IComparable, а не IComparible (a not i)
для ядра .net используйте typeof (T) .GetTypeInfo (). IsEnum
@ Скотт. Ты совершенно прав. Но, похоже, я не могу отредактировать свой комментарий. Извини.
Очень старая тема, но после C# 7.3 произошли огромные улучшения. Теперь полностью поддерживается использование ограничений Enum. Смотрите мой более длинный ответ полностью внизу.
Эта функция поддерживается начиная с C# 7.3.
Что интересно, по-видимому, это возможно на других языках (напрямую управляемый 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.
У меня есть особое требование, когда мне нужно использовать 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), даже если вы не знаете, какой это тип перечисления, только то, что объект является перечислением.
Однако предварительная проверка с помощью IsDefined испортит нечувствительность к регистру. В отличие от Parse, IsDefined не имеет аргумента ignoreCase, и MSDN говорит, что он соответствует только точному регистру.
Следующий фрагмент (из образцы 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. Стоит ли дополнительных усилий? Эм, ну, думаю, я позволю тебе решить это.
Дополнительная заслуга: оказывается, что общее ограничение 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, и знает, как функции поддерживаются на более высоком уровне языка - уровне, который многие из нас по-прежнему считают низким уровнем в приложениях, бизнес-правилах, пользовательском интерфейсе, библиотеках компонентов и т. д.
@ruslan - в первом абзаце ответа написано, что вы не можете этого сделать на C#. На самом деле это то, что показывает этот ответ: очень возможно в cil (поскольку приведенный выше код успешно работает при использовании на других языках .net), но невозможно в C# сам по себе.
Я действительно хотел бы знать, почему команда C# еще не разрешила это, поскольку это уже поддерживается MSIL.
@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.
Работая со смесью C и Ассемблера (различных) в течение многих лет по необходимости, я могу только сказать, что я держусь подальше от этих неподдерживаемых вещей. Одна вещь, которую я никогда не понимал, - это ПОЧЕМУ команда .NET не поддерживает это, если у MSIL есть возможность сделать это, и так много людей просят об этом и вынуждены писать ужасные обходные пути.
@LordofScripts: я думаю, причина в том, что, поскольку класс, который ограничивает T до System.Enum, не сможет делать с T все, что люди могут ожидать, авторы C# решили, что они могут вообще запретить это. Я считаю это решение неудачным, поскольку C# просто проигнорировал любую специальную обработку ограничений System.Enum, можно было бы написать метод расширения HasAnyFlags<T>(this T it, T other), который был бы на порядки быстрее, чем Enum.HasFlag(Enum), и который проверял бы тип своих аргументов.
@MichaelBlackburn Это сложнее, чем кажется, в основном из-за битовых флагов в перечислениях. Пользователь github по имени HaloFour дает хорошее резюме в этот выпуск Roslyn.
Только мои три цента .. Поскольку System.Enum слишком особенный, чтобы привлекать к себе внимание, и поскольку у нас уже есть where T:class, where T:struct и т. д., То where T:enum кажется довольно крутым и, вероятно, тривиальным для реализации ..
Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также. Проклятие...
Да, лучше сказать where T : struct, System.Enum, потому что тогда компилятор будет знать, что это всегда тип значения. Как только что сказал @ThatMarc, это необходимо, если вы хотите использовать T? (Nullable<T>). Это также может помочь вам в других ситуациях, таких как T t = …; if (t == null) { … }, когда компилятор выдаст полезное сообщение, если он знает, что T является типом значения.
пожалуйста, я новичок в MSIL, куда мне вставить и запустить код MSIL (хочу посмотреть, как это работает!). Спасибо!
Мне понравилось решение Кристофера Карренса с использованием 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)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));
}
}
В этом нет никакого смысла. Если TEnum на самом деле является типом Enum, а text - пустой строкой, тогда вы получите ArgumentException, в котором говорится, что «TEnum должен быть типом Enum», даже если это так.
Начиная с C# 7.3 (доступен с Visual Studio 2017 ≥ v15.7), этот код теперь полностью действителен:
public static TEnum Parse<TEnum>(string value)
where TEnum : struct, Enum
{
...
}
У вас может быть реальное принудительное ограничение перечисления компилятора, злоупотребляя наследованием ограничений. В следующем коде одновременно указываются ограничения 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? Благодарю.
@ DavidI.McIntosh EnumClassUtils<System.Enum> достаточно, чтобы ограничить T любым System.Enum и любыми производными типами. struct на Parse затем ограничивает его реальным типом перечисления. В какой-то момент вам нужно ограничиться Enum. Для этого ваш класс должен быть вложенным. См. gist.github.com/MrJul/7da12f5f2d6c69f03d79
Ах, теперь я понимаю, как это работает. Тогда использование класса всегда должно осуществляться посредством ссылки на него как на вложенный класс - неприятно, но я думаю, что это лучшее, на что можно надеяться. Кстати, спасибо за быстрый ответ.
Чтобы быть ясным, мой комментарий «неприятный» не был комментарием к вашему решению - это действительно красивый хак. Просто «неприятно», что MS заставляет нас использовать такой запутанный хак.
Есть ли способ работать с этим, чтобы его можно было использовать для методов расширения?
@Max К сожалению, я так не думаю :(
Я бы добавил внутренний конструктор, чтобы предотвратить странное наследование этого класса.
Было бы неплохо, если бы вы могли это сделать: где T: class, struct
Что здесь дает ограничение where TClass : class?
Есть ли способ еще больше ограничить TEnum, чтобы разрешить int v; TEnum e = (TEnum) v;?
Пора! И он ЛУЧШЕ поддерживает int v; TEnum e = (TEnum)v;, то есть лучше быть достаточно умным, чтобы понять, что все Enum - это целые числа! Не надоедай Microsoft!
@Trinkyo enum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int }enum AlsoNotAnInt : long { Well, Bummer }
Несмотря на то, что это работает в C# 7.3 и более поздних версиях, несколько глупо, что при наличии обнуляемого свойства типа T с ограничением where T: Enum компиляторы по-прежнему плачут, как младенец, что T должен иметь тип, не допускающий значения NULL., в результате чего во всей иерархии передаваемых дженериков необходимо указать ограничение struct также. Проклятие...
Я создал метод расширения 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
Хотя это, вероятно, работает, это почти не имеет отношения к вопросу.
Как указано в других ответах ранее; хотя это не может быть выражено в исходном коде, на самом деле это можно сделать на уровне 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().
Существующие ответы верны по состоянию на 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, очень верно, но стоит отметить, что является - часто задаваемая функция. Хорошие шансы на его появление.
Это будет в C# 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/…. :)
Пожалуйста, проголосуйте за этот ответ, теперь, когда функция существует, он должен быть намного выше в списке! :)
Также следует учитывать, что, поскольку выпуск 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 вы используете?
@MohamedElshawaf Я считаю, что это версия 15.7, которая поддерживает C# 7.3.
Я думаю, что лучше написать where T : struct, Enum, чтобы не передавать сам System.Enum в качестве параметра типа.
Как @MariuszPawelski, я пишу struct, Enum. Мое обоснование объясняется в ответе и комментариях здесь.
Информация ReSharper мне очень помогла. Обратите внимание, что последняя предварительная версия поддерживает эту функцию.
Я удивлен, что это заняло так много времени
Для полноты картины ниже представлено решение для 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;
}
}
Почему методы расширения используются только для ссылочных типов?