Як визначити перелік із рядковим значенням?


97

Я намагаюся визначити Enumта додати дійсні загальні роздільники, які використовуються у файлах CSV або подібних. Тоді я збираюся прив’язати його до ComboBoxджерела даних як джерело даних, тому кожного разу, коли я додаю або видаляю з визначення Enum, мені не потрібно буде щось змінювати у списку.

Проблема полягає в тому, як я можу визначити перелік за допомогою рядкового представлення, приблизно так:

public enum SeparatorChars{Comma = ",", Tab = "\t", Space = " "}


Відповіді:


113

Ви не можете - значення перерахування повинні бути інтегральними значеннями. Ви можете використовувати атрибути, щоб пов’язати значення рядка з кожним значенням переліку, або в цьому випадку, якщо кожен роздільник - це один символ, ви можете просто використовувати charзначення:

enum Separator
{
    Comma = ',',
    Tab = '\t',
    Space = ' '
}

(РЕДАГУВАТИ: Просто для уточнення, ви не можете створити charбазовий тип перечислення, але ви можете використовувати charконстанти, щоб призначити інтегральне значення, яке відповідає кожному значенню перерахування. Основним типом вищезазначеного перерахування є int.)

Тоді метод розширення, якщо він вам потрібен:

public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}

Char не діє в переліках. Msdn: "Кожен тип перелічення має базовий тип, який може бути будь-яким цілісним типом, крім символу."
часомдля

8
@dogetherfor: Ти можеш використовувати літерал char для значення , згідно з моєю відповіддю. Я тестував :)
Джон Скіт,

оскільки ця вимога стосується файлів, користувачеві може знадобитися роздільник CRLF. Чи спрацює це і в цьому випадку?
Maheep

Дякую, Джон, \ t не вважається знаком ?!
Саїд Яздані

1
@ShaunLuttin: перелічення - це просто «іменовані числа» - отже, перерахування рядків насправді зовсім не вписується в цю модель.
Джон Скіт,

83

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

public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}

9
Недоліком цього підходу, протилежного іншим, є те, що ви не можете перерахувати їх, не зробивши щось зайве / особливе.
cesay

Це не допомагає застосувати певні значення під час компіляції, оскільки separatorтепер це рядок (може бути будь-яким) замість Separatorтипу з обмеженими дійсними значеннями.
ChickenFeet

73

Ви можете цього досягти, але для цього потрібно трохи попрацювати.

  1. Визначте клас атрибута, який міститиме значення рядка для переліку.
  2. Визначте метод розширення, який поверне значення з атрибута. Наприклад..GetStringValue (це значення Enum) поверне значення атрибута.
  3. Тоді ви можете визначити перелік таким чином ..
public enum Test: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Щось = 2        
} 
  1. Щоб повернути значення з Attrinbute Test.Foo.GetStringValue ();

Посилання: Enum із рядковими значеннями в C #


5
Я знаю це старе, але воно, очевидно, унікальне і дозволяє використовувати перерахування в коді та значення рядків у БД. Дивовижно
A_kat

1
Ще один пізній коментар, але це справді геніальне рішення
Алан

36

Для простого переліку рядкових значень (або будь-якого іншого типу):

public static class MyEnumClass
{
    public const string 
        MyValue1 = "My value 1",
        MyValue2 = "My value 2";
}

Використання: string MyValue = MyEnumClass.MyValue1;


1
Хоча це не перерахування, я думаю, це може надати найкраще рішення для того, що намагається зробити користувач. Іноді найкраще найпростіше рішення.
Zesty

29

Ви не можете зробити це з переліченнями, але ви можете зробити це так:

public static class SeparatorChars
{
    public static string Comma = ",";

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 Хоча я вважаю, що це правильне рішення, я б змінив назву класу або змінив тип на символи. Просто щоб бути послідовними.
часомдля

Дякую, можете сказати, що буде еквівалентом comboBox.DataSource = Enum.GetValues(typeof(myEnum));у цьому випадку?
Саїд Яздані

1
@ Sean87: Я хотів би це мати, я би взяв відповідь JonSkeets.
Фішерман

Я думаю, що це майже правильна відповідь, оскільки вона не придатна для використання всередині switch-caseблоків. Поля повинні бути constв порядку. Але все одно не можна допомогти, якщо ти хочеш Enum.GetValues(typeof(myEnum)).
Андре Сантало,

7
Я б використав constзамість static. Константи доступні лише для читання, а також статичні і не можуть бути призначені в конструкторах (якщо тільки поля не для читання).
Олів'є Жако-Декомб

12

Ви не можете, тому що enum може базуватися лише на примітивному числовому типі. Ви можете спробувати використовувати Dictionaryзамість цього:

Dictionary<String, char> separators = new Dictionary<string, char>
{
    {"Comma", ','}, 
    {"Tab",  '\t'}, 
    {"Space", ' '},
};

Крім того, ви можете використати Dictionary<Separator, char>або Dictionary<Separator, string>де Separatorнормальний перелік:

enum Separator
{
    Comma,
    Tab,
    Space
}

що було б трохи приємніше, ніж обробляти рядки безпосередньо.


11

Клас, який імітує поведінку перелічення, але використовуючи stringзамість, intможна створити наступним чином ...

public class GrainType
{
    private string _typeKeyWord;

    private GrainType(string typeKeyWord)
    {
        _typeKeyWord = typeKeyWord;
    }

    public override string ToString()
    {
        return _typeKeyWord;
    }

    public static GrainType Wheat = new GrainType("GT_WHEAT");
    public static GrainType Corn = new GrainType("GT_CORN");
    public static GrainType Rice = new GrainType("GT_RICE");
    public static GrainType Barley = new GrainType("GT_BARLEY");

}

Використання ...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

тоді...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}

Єдине, наприклад, не можна робити GrainType myGrain = "GT_CORN".
colmde

Ви могли б, якщо перевизначили оператора
SSX-SL33PY

8

Запізно відповісти, але, можливо, це допоможе комусь у майбутньому. Мені стало простіше використовувати struct для такого роду проблем.

Наступний зразок - це копія, вставлена ​​частина з коду MS:

namespace System.IdentityModel.Tokens.Jwt
{
    //
    // Summary:
    //     List of registered claims from different sources http://tools.ietf.org/html/rfc7519#section-4
    //     http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    public struct JwtRegisteredClaimNames
    {
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Actort = "actort";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Typ = "typ";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Sub = "sub";
        //
        // Summary:
        //     http://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout
        public const string Sid = "sid";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Prn = "prn";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nbf = "nbf";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nonce = "nonce";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string NameId = "nameid";

    }
}

Не могли б ви пояснити, чому цей підхід кращий, ніж використання класу?
Херардо Гриньолі,

@GerardoGrignoli Я точно не знаю, чому вони використовують структуру замість класу в MS для такого роду речей. Я навіть не намагався це з’ясувати, оскільки це для мене прекрасно працює. Можливо, спробуйте задати питання тут, у стеці ...
suchoss

6

Можливо, вже пізно, але ось воно йде.

Ми можемо використовувати атрибут EnumMember для управління значеннями Enum.

public enum EUnitOfMeasure
{
    [EnumMember(Value = "KM")]
    Kilometer,
    [EnumMember(Value = "MI")]
    Miles
}

Таким чином, значення результату для EUnitOfMeasure буде становити KM або MI. Це також видно з відповіді Ендрю Вітакер .


5

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

Наступний підхід працює, коли ви не довели до кінця enum namesбажане і enum valuesє stringподанням enam name; використовуйте nameof()для спрощення рефакторингу.

public static class Colours
{
    public static string Red => nameof(Red);
    public static string Green => nameof(Green);
    public static string Blue => nameof(Blue);
}

Це досягає наміру переліку, що має рядкові значення (наприклад, такий псевдокод):

public enum Colours
{
    "Red",
    "Green",
    "Blue"
}

4

Я створив базовий клас для створення рядкових перерахувань у .NET. Це лише один файл C #, який ви можете скопіювати та вставити у свої проекти або встановити за допомогою пакета NuGet з іменем StringEnum .

Використання:

///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = New("#FF0000");
    public static readonly HexColor Green = New("#00FF00");
    public static readonly HexColor Red = New("#000FF");
}

Особливості

  • Ваш StringEnum виглядає дещо схожим на звичайний перелік:
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)
  • Intellisense запропонує ім'я переліку, якщо клас анотовано коментарем xml <completitionlist>. (Працює як на C #, так і на VB): тобто

Демонстрація Intellisense

Встановлення

Або:

  • Встановіть найновіший пакет StringEnum NuGet, який базується на .Net Standard 1.0тому, що він працює на .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 тощо.
  • Або вставте наступний базовий клас StringEnum у свій проект. ( остання версія )
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static IList<T> valueList = new List<T>();
        protected static T New(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueList.Add(result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T Parse(string value, bool caseSensitive = false)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T TryParse(string value, bool caseSensitive = false)
        {
            if (value == null) return null;
            if (valueList.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            var field = valueList.FirstOrDefault(f => f.Value.Equals(value,
                    caseSensitive ? StringComparison.Ordinal
                                  : StringComparison.OrdinalIgnoreCase));
            // Not using InvariantCulture because it's only supported in NETStandard >= 2.0

            if (field == null)
                return null;

            return field;
        }
    }
  • Для Newtonsoft.Jsonпідтримки серіалізації скопіюйте цю розширену версію. StringEnum.cs

Я зрозумів, що цей код схожий на відповідь Бена. Я щиро писав це з нуля. Однак я думаю, що у нього є кілька додаткових функцій, наприклад, <completitionlist>хак, отриманий клас більше схожий на Enum, не використовуючи роздумів про Parse (), пакет NuGet та репо, де я, сподіваюся, розгляну вхідні проблеми та відгуки.


3

Спираючись на деякі відповіді тут, я реалізував багаторазовий базовий клас, що імітує поведінку перерахування, але з stringосновним типом. Він підтримує різні операції, включаючи:

  1. отримання списку можливих значень
  2. перетворення в рядок
  3. порівняння з іншими екземплярами допомоги .Equals, ==і!=
  4. перетворення в / з JSON за допомогою JSON.NET JsonConverter

Це базовий клас у цілому:

public abstract class StringEnumBase<T> : IEquatable<T>
    where T : StringEnumBase<T>
{
    public string Value { get; }

    protected StringEnumBase(string value) => this.Value = value;

    public override string ToString() => this.Value;

    public static List<T> AsList()
    {
        return typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Static)
            .Where(p => p.PropertyType == typeof(T))
            .Select(p => (T)p.GetValue(null))
            .ToList();
    }

    public static T Parse(string value)
    {
        List<T> all = AsList();

        if (!all.Any(a => a.Value == value))
            throw new InvalidOperationException($"\"{value}\" is not a valid value for the type {typeof(T).Name}");

        return all.Single(a => a.Value == value);
    }

    public bool Equals(T other)
    {
        if (other == null) return false;
        return this.Value == other?.Value;
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (obj is T other) return this.Equals(other);
        return false;
    }

    public override int GetHashCode() => this.Value.GetHashCode();

    public static bool operator ==(StringEnumBase<T> a, StringEnumBase<T> b) => a?.Equals(b) ?? false;

    public static bool operator !=(StringEnumBase<T> a, StringEnumBase<T> b) => !(a?.Equals(b) ?? false);

    public class JsonConverter<T> : Newtonsoft.Json.JsonConverter
        where T : StringEnumBase<T>
    {
        public override bool CanRead => true;

        public override bool CanWrite => true;

        public override bool CanConvert(Type objectType) => ImplementsGeneric(objectType, typeof(StringEnumBase<>));

        private static bool ImplementsGeneric(Type type, Type generic)
        {
            while (type != null)
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == generic)
                    return true;

                type = type.BaseType;
            }

            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken item = JToken.Load(reader);
            string value = item.Value<string>();
            return StringEnumBase<T>.Parse(value);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value is StringEnumBase<T> v)
                JToken.FromObject(v.Value).WriteTo(writer);
        }
    }
}

І ось як би ви реалізували свій "перелік рядків":

[JsonConverter(typeof(JsonConverter<Colour>))]
public class Colour : StringEnumBase<Colour>
{
    private Colour(string value) : base(value) { }

    public static Colour Red => new Colour("red");
    public static Colour Green => new Colour("green");
    public static Colour Blue => new Colour("blue");
}

Що можна використовувати так:

public class Foo
{
    public Colour colour { get; }

    public Foo(Colour colour) => this.colour = colour;

    public bool Bar()
    {
        if (this.colour == Colour.Red || this.colour == Colour.Blue)
            return true;
        else
            return false;
    }
}

Сподіваюся, комусь це стане в нагоді!


2

Ну спершу ви намагаєтеся призначити рядки, а не символи, навіть якщо це лише один символ. використовуйте ',' замість ",". Наступне, перелічення беруть лише цілісні типи, не charвикористовуючи значення Unicode, але я настійно рекомендую вам цього не робити. Якщо ви впевнені, що ці значення залишаються незмінними, в різних культурах та мовах, я б використовував статичний клас із рядками const.


2

Хоча насправді неможливо використовувати a charабо astring як основу для перерахування, я думаю, це не те, що ви насправді любите робити.

Як ви вже згадували, ви хотіли б мати перелік можливостей і показати це рядкове представлення цього у списку. Якщо користувач вибере одне з цих подань рядків, ви хочете отримати відповідний перелік. І це можливо:

Спочатку ми повинні зв’язати деякий рядок зі значенням переліку. Це можна зробити, використовуючи те, DescriptionAttributeщо описано тут або тут .

Тепер вам потрібно створити список значень переліку та відповідні описи. Це можна зробити за допомогою наступного методу:

/// <summary>
/// Creates an List with all keys and values of a given Enum class
/// </summary>
/// <typeparam name="T">Must be derived from class Enum!</typeparam>
/// <returns>A list of KeyValuePair&lt;Enum, string&gt; with all available
/// names and values of the given Enum.</returns>
public static IList<KeyValuePair<T, string>> ToList<T>() where T : struct
{
    var type = typeof(T);

    if (!type.IsEnum)
    {
        throw new ArgumentException("T must be an enum");
    }

    return (IList<KeyValuePair<T, string>>)
            Enum.GetValues(type)
                .OfType<T>()
                .Select(e =>
                {
                    var asEnum = (Enum)Convert.ChangeType(e, typeof(Enum));
                    return new KeyValuePair<T, string>(e, asEnum.Description());
                })
                .ToArray();
}

Тепер у вас буде список пар значень ключів усіх перерахувань та їх опис. Тож давайте просто призначимо це як джерело даних для поля зі списком.

var comboBox = new ComboBox();
comboBox.ValueMember = "Key"
comboBox.DisplayMember = "Value";
comboBox.DataSource = EnumUtilities.ToList<Separator>();

comboBox.SelectedIndexChanged += (sender, e) =>
{
    var selectedEnum = (Separator)comboBox.SelectedValue;
    MessageBox.Show(selectedEnum.ToString());
}

Користувач бачить усі рядкові подання переліку, і в коді ви отримаєте бажане значення переліку.


0

Ми не можемо визначити перелік як тип рядка. Затвердженими типами перерахування є байт, сбайт, короткий, ushort, int, uint, long або ulong.

Якщо вам потрібні додаткові відомості про перелік, перейдіть за посиланням нижче, це посилання допоможе вам зрозуміти перелік. Перерахування

@ narendras1414


0

Це працює для мене ..

   public class ShapeTypes
    {
        private ShapeTypes() { }
        public static string OVAL
        {
            get
            {
                return "ov";
            }
            private set { }
        }

        public static string SQUARE
        {
            get
            {
                return "sq";
            }
            private set { }
        }

        public static string RECTANGLE
        {
            get
            {
                return "rec";
            }
            private set { }
        }
    }

0

Те, що я нещодавно почав робити, це використання Tuples

public static (string Fox, string Rabbit, string Horse) Animals = ("Fox", "Rabbit", "Horse");
...
public static (string Comma, string Tab, string Space) SeparatorChars = (",", "\t", " ");

-1

Клас виплат

 public sealed class GenericDateTimeFormatType
    {

        public static readonly GenericDateTimeFormatType Format1 = new GenericDateTimeFormatType("dd-MM-YYYY");
        public static readonly GenericDateTimeFormatType Format2 = new GenericDateTimeFormatType("dd-MMM-YYYY");

        private GenericDateTimeFormatType(string Format)
        {
            _Value = Format;
        }

        public string _Value { get; private set; }
    }

Оплата споживання

public static void Main()
{
       Country A = new Country();

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

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