Связывание перечислений со строками в С#
Я знаю, что следующее невозможно, потому что тип Enumeration должен быть int
enum GroupTypes
{
TheGroup = "OEM",
TheOtherGroup = "CMB"
}
Из моей базы данных я получаю поле с непонятными кодами (OEM
и CMB
). Я хотел бы превратить это поле в enum
или что-то еще понятное. Потому что, если целью является удобочитаемость, решение должно быть кратким.
Какие еще варианты у меня есть?
Ответы
Ответ 1
Мне нравится использовать свойства в классе вместо методов, так как они выглядят как enum.
Вот пример для Logger:
public class LogCategory
{
private LogCategory(string value) { Value = value; }
public string Value { get; set; }
public static LogCategory Trace { get { return new LogCategory("Trace"); } }
public static LogCategory Debug { get { return new LogCategory("Debug"); } }
public static LogCategory Info { get { return new LogCategory("Info"); } }
public static LogCategory Warning { get { return new LogCategory("Warning"); } }
public static LogCategory Error { get { return new LogCategory("Error"); } }
}
Передайте типобезопасные строковые значения в качестве параметра:
public static void Write(string message, LogCategory logCategory)
{
var log = new LogEntry { Message = message };
Logger.Write(log, logCategory.Value);
}
Использование:
Logger.Write("This is almost like an enum.", LogCategory.Info);
Ответ 2
Вы также можете использовать модель расширения:
public enum MyEnum
{
[Description("String 1")]
V1= 1,
[Description("String 2")]
V2= 2
}
Ваш дополнительный класс
public static class MyEnumExtensions
{
public static string ToDescriptionString(this MyEnum val)
{
DescriptionAttribute[] attributes = (DescriptionAttribute[])val
.GetType()
.GetField(val.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
использование:
MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());
Ответ 3
Как насчет использования статического класса с константами?
static class GroupTypes
{
public const string TheGroup = "OEM";
public const string TheOtherGroup = "CMB";
}
void DoSomething(string groupType)
{
if(groupType == GroupTypes.TheGroup)
{
// Be nice
}
else if (groupType == GroupTypes.TheOtherGroup)
{
// Continue to be nice
}
else
{
// unexpected, throw exception?
}
}
Ответ 4
Вы можете добавлять атрибуты к элементам перечисления, а затем использовать отражение для получения значений из атрибутов.
Вам нужно будет использовать спецификатор "field" для применения атрибутов, например:
enum GroupTypes
{
[field:Description("OEM")]
TheGroup,
[field:Description("CMB")]
TheOtherGroup
}
Затем вы будете размышлять о статических полях типа перечисления (в данном случае GroupTypes) и получить DescriptionAttribute
для значение, которое вы искали, используя отражение:
public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
this T value) where T : struct
{
// The type of the enum, it will be reused.
Type type = typeof(T);
// If T is not an enum, get out.
if (!type.IsEnum)
throw new InvalidOperationException(
"The type parameter T must be an enum type.");
// If the value isn't defined throw an exception.
if (!Enum.IsDefined(type, value))
throw new InvalidEnumArgumentException(
"value", Convert.ToInt32(value), type);
// Get the static field for the value.
FieldInfo fi = type.GetField(value.ToString(),
BindingFlags.Static | BindingFlags.Public);
// Get the description attribute, if there is one.
return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
Cast<DescriptionAttribute>().SingleOrDefault();
}
Я решил вернуть сам DescriptionAttribute
, если вы хотите определить, применяется ли этот атрибут даже.
Ответ 5
Вы можете сделать это очень легко. Используйте следующий код.
enum GroupTypes
{
OEM,
CMB
};
Затем, когда вы хотите получить строковое значение каждого элемента перечисления, просто используйте следующую строку кода.
String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);
Я использовал этот метод в прошлом, и я также использовал класс констант для хранения строковых констант, оба они работают очень хорошо, но я предпочитаю это.
Ответ 6
Попробуйте добавить константы в статический класс. Вы не получите Type, но у вас будут читабельные, упорядоченные константы:
public static class GroupTypes {
public const string TheGroup = "OEM";
public const string TheOtherGroup = "CMB";
}
Ответ 7
Создайте второе перечисление для вашей БД, содержащей следующее:
enum DBGroupTypes
{
OEM = 0,
CMB = 1
}
Теперь вы можете использовать Enum.Parse для получения правильного значения DBGroupTypes из строк "OEM" и "CMB". Затем вы можете преобразовать их в int и получить правильные значения из нужного перечисления, которое вы хотите использовать далее в своей модели.
Ответ 8
Использовать класс.
Изменить: лучший пример
class StarshipType
{
private string _Name;
private static List<StarshipType> _StarshipTypes = new List<StarshipType>();
public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
public static readonly StarshipType Light = new StarshipType("Light");
public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
public static readonly StarshipType Heavy = new StarshipType("Heavy");
public static readonly StarshipType Superheavy = new StarshipType("Superheavy");
public string Name
{
get { return _Name; }
private set { _Name = value; }
}
public static IList<StarshipType> StarshipTypes
{
get { return _StarshipTypes; }
}
private StarshipType(string name, int systemRatio)
{
Name = name;
_StarshipTypes.Add(this);
}
public static StarshipType Parse(string toParse)
{
foreach (StarshipType s in StarshipTypes)
{
if (toParse == s.Name)
return s;
}
throw new FormatException("Could not parse string.");
}
}
Ответ 9
Вот метод расширения, который я использовал для получения значения перечисления как строки. Сначала здесь перечисление.
public enum DatabaseEnvironment
{
[Description("AzamSharpBlogDevDatabase")]
Development = 1,
[Description("AzamSharpBlogQADatabase")]
QualityAssurance = 2,
[Description("AzamSharpBlogTestDatabase")]
Test = 3
}
Атрибут Описание появился в System.ComponentModel.
И вот мой метод расширения:
public static string GetValueAsString(this DatabaseEnvironment environment)
{
// get the field
var field = environment.GetType().GetField(environment.ToString());
var customAttributes = field.GetCustomAttributes(typeof (DescriptionAttribute), false);
if(customAttributes.Length > 0)
{
return (customAttributes[0] as DescriptionAttribute).Description;
}
else
{
return environment.ToString();
}
}
Теперь вы можете получить доступ к перечислению как строковое значение, используя следующий код:
[TestFixture]
public class when_getting_value_of_enum
{
[Test]
public void should_get_the_value_as_string()
{
Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString());
}
}
Ответ 10
Другой способ справиться с проблемой - это иметь enum и массив строк, которые будут отображать значения enum в список строк:
public enum GroupTypes
{
TheGroup = 0,
TheOtherGroup
}
string[] GroupTypesStr = {
"OEM",
"CMB"
};
Вы можете использовать это примерно так:
Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);
Это подскажет CMB
ПЛЮСЫ:
- Простой и чистый код.
- Высокая производительность (особенно по сравнению с теми подходами, которые использует классы)
МИНУСЫ:
- Склонен перепутать список при редактировании, но это будет хорошо для короткого списка.
Ответ 11
Считаете ли вы справочную таблицу с использованием словаря?
enum GroupTypes
{
TheGroup,
TheOtherGroup
}
Dictionary<string, GroupTypes> GroupTypeLookup = new Dictionary<string, GroupTypes>();
// initialize lookup table:
GroupTypeLookup.Add("OEM", TheGroup);
GroupTypeLookup.Add("CMB", TheOtherGroup);
Затем вы можете использовать GroupTypeLookup.TryGetValue() для поиска строки при ее прочтении.
Ответ 12
Ответа на этот вопрос Even:
public class LogCategory
{
private LogCategory(string value) { Value = value; }
public string Value { get; set; }
public static LogCategory Trace { get { return new LogCategory("Trace"); } }
public static LogCategory Debug { get { return new LogCategory("Debug"); } }
public static LogCategory Info { get { return new LogCategory("Info"); } }
public static LogCategory Warning { get { return new LogCategory("Warning"); } }
public static LogCategory Error { get { return new LogCategory("Error"); } }
}
Просто захотелось добавить способ имитации переключателя с перечислениями на основе классов:
public void Foo(LogCategory logCategory){
var @switch = new Dictionary<LogCategory, Action>{
{LogCategory.Trace, ()=>Console.Writeline("Trace selected!")},
{LogCategory.Debug, ()=>Console.Writeline("Debug selected!")},
{LogCategory.Error, ()=>Console.Writeline("Error selected!")}};
//will print one of the line based on passed argument
@switch[logCategory]();
}
Ответ 13
С# не поддерживает перечисляемые строки, но для большинства ситуаций вы можете использовать List или Dictionary для получения желаемого эффекта.
например. Чтобы распечатать результаты прохода/сбоя:
List<string> PassFail = new List<string> { "FAIL", "PASS" };
bool result = true;
Console.WriteLine("Test1: " + PassFail[result.GetHashCode()]);
Ответ 14
Я бы просто создал словарь и использовал код в качестве ключа.
Изменить: Чтобы ответить на комментарий об обратном поиске (поиск ключа), это не будет ужасно эффективным. Если это необходимо, я бы написал новый класс для его обработки.
Ответ 15
Я бы сделал это в классе, избегая перечисления вообще. И затем с помощью манипулятора типа вы можете создать объект, когда вы его возьмете из db.
IE:
public class Group
{
public string Value{ get; set; }
public Group( string value ){ Value = value; }
public static Group TheGroup() { return new Group("OEM"); }
public static Group OtherGroup() { return new Group("CMB"); }
}
Ответ 16
Мой первый вопрос: есть ли у вас доступ к самой базе данных? Это должно быть нормализовано в базе данных, в идеале, в противном случае любое решение будет подвержено ошибкам. По моему опыту, поля данных, полные "OEM" и "CMB", как правило, включают в себя такие вещи, как "oem" и другие "дерьмовые данные", смешанные со временем... Если вы можете нормализовать его, вы можете использовать ключ в таблице, содержащей элементы как ваш Enum, и вы закончили, с гораздо более чистой структурой.
Если это не доступно, я сделаю ваш Enum и сделаю класс для анализа вашей строки в Enum для вас. Это, по крайней мере, даст вам некоторую гибкость при обработке нестандартных записей и гораздо большей гибкости при захвате или обработке ошибок, чем при использовании любого из обходных решений с использованием Enum.Parse/Reflection/etc. Словарь будет работать, но может сломаться, если у вас когда-нибудь возникнут проблемы и т.д.
Я бы рекомендовал написать класс, чтобы вы могли:
// I renamed this to GroupType, since it sounds like each element has a single type...
GroupType theType = GroupTypeParser.GetGroupType(theDBString);
Это сохраняет большую часть вашей читаемости без необходимости изменения БД.
Ответ 17
Если я правильно понял, вам нужно преобразовать строку в enum:
enum GroupTypes {
Unknown = 0,
OEM = 1,
CMB = 2
}
static GroupTypes StrToEnum(string str){
GroupTypes g = GroupTypes.Unknown;
try {
object o = Enum.Parse(typeof(GroupTypes), str, true);
g = (GroupTypes)(o ?? 0);
} catch {
}
return g;
}
// then use it like this
GroupTypes g1 = StrToEnum("OEM");
GroupTypes g2 = StrToEnum("bad value");
Если вы захотите, вы можете сделать его более интересным с дженериками для типа перечисления.
Ответ 18
В VS 2015 вы можете использовать nameof
public class LogCategory
{
public static string Trace;
public static string Debug;
public static string Info;
public static string Warning;
public static string Error;
}
Использование:
Logger.Write("This is almost like an enum.", nameof(LogCategory.Info));
Ответ 19
Небольшая настройка метода Glennular Extension, поэтому вы можете использовать расширение для других вещей, кроме ENUM,
using System;
using System.ComponentModel;
namespace Extensions {
public static class T_Extensions {
/// <summary>
/// Gets the Description Attribute Value
/// </summary>
/// <typeparam name="T">Entity Type</typeparam>
/// <param name="val">Variable</param>
/// <returns>The value of the Description Attribute or an Empty String</returns>
public static string Description<T>(this T t) {
DescriptionAttribute[] attributes = (DescriptionAttribute[])t.GetType().GetField(t.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
}
Или используя Linq
using System;
using System.ComponentModel;
using System.Linq;
namespace Extensions {
public static class T_Extensions {
public static string Description<T>(this T t) =>
((DescriptionAttribute[])t
?.GetType()
?.GetField(t?.ToString())
?.GetCustomAttributes(typeof(DescriptionAttribute), false))
?.Select(a => a?.Description)
?.FirstOrDefault()
?? string.Empty;
}
}
Ответ 20
public class DataType
{
private readonly string value;
private static readonly Dictionary<string, DataType> predefinedValues;
public static readonly DataType Json = new DataType("json");
public static readonly DataType Xml = new DataType("xml");
public static readonly DataType Text = new DataType("text");
public static readonly DataType Html = new DataType("html");
public static readonly DataType Binary = new DataType("binary");
static DataType()
{
predefinedValues = new Dictionary<string, DataType>();
predefinedValues.Add(Json.Value, Json);
predefinedValues.Add(Xml.Value, Xml);
predefinedValues.Add(Text.Value, Text);
predefinedValues.Add(Html.Value, Html);
predefinedValues.Add(Binary.Value, Binary);
}
private DataType(string value)
{
this.value = value;
}
public static DataType Parse(string value)
{
var exception = new FormatException($"Invalid value for type {nameof(DataType)}");
if (string.IsNullOrEmpty(value))
throw exception;
string key = value.ToLower();
if (!predefinedValues.ContainsKey(key))
throw exception;
return predefinedValues[key];
}
public string Value
{
get { return value; }
}
}
Ответ 21
перечисления в С# ограничены базовыми целыми числовыми типами (байт, sbyte, short, ushort, int, uint, long и ulong). Вы не можете связать их с базовым значением, основанным на символах или строках.
Другой подход может заключаться в определении словаря типа Dictionary < string, string > .
Ответ 22
Если вы пытаетесь сделать свой код доступным для чтения:
class GroupTypes {
public static final String (whatever oem stands for) = "OEM";
public static final String (whatever cmb stands for) = "CMB";
...
}
и если вам нужен список из них, включите эти финалы в статическом финальном списке <String> . Этот пример приведен в Java.
Если вы пытаетесь сделать ваше приложение доступным для чтения, добавьте:
public static final Map<String, String> groupsByDbValue;
static {
groupsByDbValue = new HashMap<String, String>();
groupsByDbValue.put("OEM", "(whatever OEM stands for)");
groupsByDbValue.put("CMB", "(whatever CMB stands for)");
}
Ответ 23
Я даже выполнил несколько перечислений, предложенных @Even (через членов class X
и public static X
), чтобы узнать позже, что в эти дни, начиная с .Net 4.5, есть право ToString()
.
Теперь я переопределяю все обратно на перечисления.
Ответ 24
Вы можете использовать две перечисления. Один для базы данных и другой для удобочитаемости.
Вам просто нужно убедиться, что они остаются в синхронизации, что кажется небольшой ценой.
Вам не нужно устанавливать значения, просто устанавливайте позиции одинаково, но при установке значений очень ясно, что два перечисления связаны друг с другом и препятствуют перегруппировке элементов перечисления. И комментарий позволяет экипажу технического обслуживания знать, что они связаны и должны храниться в синхронизации.
// keep in sync with GroupTypes
public enum GroupTypeCodes
{
OEM,
CMB
}
// keep in sync with GroupTypesCodes
public enum GroupTypes
{
TheGroup = GroupTypeCodes.OEM,
TheOtherGroup = GroupTypeCodes.CMB
}
Чтобы использовать его, вы сначала конвертируете в код:
GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = ((GroupTypeCodes)myGroupType).ToString();
Затем, если вы хотите сделать его еще более удобным, вы можете добавить функцию расширения, которая работает только для этого типа перечисления:
public static string ToString(this GroupTypes source)
{
return ((GroupTypeCodes)source).ToString();
}
и вы можете просто сделать:
GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = myGroupType.ToString();
Ответ 25
Это способ использовать его как строго типизированный параметр или как строку:
public class ClassLikeEnum
{
public string Value
{
get;
private set;
}
ClassLikeEnum(string value)
{
Value = value;
}
public static implicit operator string(ClassLikeEnum c)
{
return c.Value;
}
public static readonly ClassLikeEnum C1 = new ClassLikeEnum("RandomString1");
public static readonly ClassLikeEnum C2 = new ClassLikeEnum("RandomString2");
}
Ответ 26
Я в основном искал ответ Reflection от @ArthurC
Просто чтобы немного расширить его ответ, вы можете сделать его еще лучше, используя универсальную функцию:
// If you want for a specific Enum
private static string EnumStringValue(GroupTypes e)
{
return EnumStringValue<GroupTypes>(e);
}
// Generic
private static string EnumStringValue<T>(T enumInstance)
{
return Enum.GetName(typeof(T), enumInstance);
}
Тогда вы можете просто обернуть все, что у вас есть
EnumStringValue(GroupTypes.TheGroup) // if you incorporate the top part
или же
EnumStringValue<GroupTypes>(GroupTypes.TheGroup) // if you just use the generic
Ответ 27
Добавление этого класса
public class DatabasePreference {
public DatabasePreference([CallerMemberName] string preferenceName = "") {
PreferenceName = preferenceName;
}
public string PreferenceName;
}
Эта работа использует CallerMemberName
для минимизации кодирования
Использование:
//Declare names
public static DatabasePreference ScannerDefaultFlashLight = new DatabasePreference();
public static DatabasePreference ScannerQrCodes = new DatabasePreference();
public static DatabasePreference Scanner1dCodes = new DatabasePreference();
Проверьте это:
Console.WriteLine(ScannerDefaultFlashLight.PreferenceName);
Console.WriteLine(ScannerDefaultFlashLight.Scanner1dCodes);
выход:
ScannerDefaultFlashLight
Scanner1dCodes
Ответ 28
Основываясь на других мнениях, это то, что я придумал. Этот подход позволяет избежать необходимости вводить значение .Value, где вы хотите получить постоянное значение.
У меня есть базовый класс для всех перечислений строк, таких как:
using System;
using Newtonsoft.Json;
[JsonConverter(typeof(ConstantConverter))]
public class StringEnum: IConvertible
{
public string Value { get; set; }
protected StringEnum(string value)
{
Value = value;
}
public static implicit operator string(StringEnum c)
{
return c.Value;
}
public string ToString(IFormatProvider provider)
{
return Value;
}
public TypeCode GetTypeCode()
{
throw new NotImplementedException();
}
public bool ToBoolean(IFormatProvider provider)
{
throw new NotImplementedException();
}
//The same for all the rest of IConvertible methods
}
JsonConverter выглядит следующим образом:
using System;
using Newtonsoft.Json;
class ConstantConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
serializer.Serialize(writer, null);
}
else
{
serializer.Serialize(writer, value.ToString());
}
}
}
И фактическое перечисление строки будет выглядеть примерно так:
public sealed class Colors : StringEnum
{
public static Colors Red { get { return new Catalog("Red"); } }
public static Colors Yellow { get { return new Catalog("Yellow"); } }
public static Colors White { get { return new Catalog("White"); } }
private Colors(string value) : base(value) { }
}
И с этим вы можете просто использовать Color.Red для сериализации в json без использования свойства Value
Ответ 29
Мне не нужно было ничего надежного, как сохранение строки в атрибутах. Мне просто нужно было превратить что-то вроде MyEnum.BillEveryWeek
в "счет каждую неделю" или MyEnum.UseLegacySystem
в "использовать унаследованную систему" - в основном разбивать перечисление на его верблюжьей оболочке на отдельные слова в нижнем регистре.
public static string UnCamelCase(this Enum input, string delimiter = " ", bool preserveCasing = false)
{
var characters = input.ToString().Select((x, i) =>
{
if (i > 0 && char.IsUpper(x))
{
return delimiter + x.ToString(CultureInfo.InvariantCulture);
}
return x.ToString(CultureInfo.InvariantCulture);
});
var result = preserveCasing
? string.Concat(characters)
: string.Concat(characters).ToLower();
var lastComma = result.LastIndexOf(", ", StringComparison.Ordinal);
if (lastComma > -1)
{
result = result.Remove(lastComma, 2).Insert(lastComma, " and ");
}
return result;
}
MyEnum.UseLegacySystem.UnCamelCase()
выходы "использовать устаревшую систему"
Если у вас установлено несколько флагов, это превратит это в простой английский (с запятой, кроме "и" вместо последней запятой).
var myCustomerBehaviour = MyEnum.BillEveryWeek | MyEnum.UseLegacySystem | MyEnum.ChargeTaxes;
Console.WriteLine(myCustomerBehaviour.UnCamelCase());
//outputs "bill every week, use legacy system and charge taxes"
Ответ 30
скажем, что у вас есть перечисление следующим образом:
enum GroupTypes
{
[Description("TheGroup")]
TheGroup,
[Description("TheOtherGroup")]
TheOtherGroup
}
После этого, если вы хотите, чтобы строковое значение вместо int (index), просто сделайте .ToString(), и вы получите значение:
Debug.WriteLine(GroupTypes.TheGroup.ToString());
Выше инструкция выведет TheGroup NOT index (int).
Надеюсь, это поможет решить вашу проблему.