Могут ли мои перечисления иметь дружеские имена?

У меня есть следующий enum

public enum myEnum
{
    ThisNameWorks, 
    This Name doesn't work
    Neither.does.this;
}

Невозможно ли иметь enum с "дружественными именами"?

Ответ 1

Имена значений enum должны соответствовать тем же правилам именования, что и все идентификаторы в С#, поэтому верно только имя.

Ответ 2

Вы можете использовать атрибут " Description, как предложил Юрий. Следующий способ расширения упрощает получение описания для заданного значения перечисления:

public static string GetDescription(this Enum value)
{
    Type type = value.GetType();
    string name = Enum.GetName(type, value);
    if (name != null)
    {
        FieldInfo field = type.GetField(name);
        if (field != null)
        {
            DescriptionAttribute attr = 
                   Attribute.GetCustomAttribute(field, 
                     typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attr != null)
            {
                return attr.Description;
            }
        }
    }
    return null;
}

Вы можете использовать его следующим образом:

public enum MyEnum
{
    [Description("Description for Foo")]
    Foo,
    [Description("Description for Bar")]
    Bar
}

MyEnum x = MyEnum.Foo;
string description = x.GetDescription();

Ответ 3

Если у вас есть следующее перечисление:

public enum MyEnum {
    First,
    Second,
    Third
}

Вы можете объявить методы расширения для MyEnum (например, для любого другого типа). Я просто взбивал это:

namespace Extension {
    public static class ExtensionMethods {
        public static string EnumValue(this MyEnum e) {
            switch (e) {
                case MyEnum.First:
                    return "First Friendly Value";
                case MyEnum.Second:
                    return "Second Friendly Value";
                case MyEnum.Third:
                    return "Third Friendly Value";
            }
            return "Horrible Failure!!";
        }
    }
}

С помощью этого метода расширения в настоящее время разрешено следующее:

Console.WriteLine(MyEnum.First.EnumValue());

Надеюсь, это поможет!

Ответ 4

Нет, но вы можете использовать DescriptionAttribute, чтобы выполнить то, что вы ищете.

Ответ 5

Вы можете использовать атрибут Description чтобы получить это дружественное имя. Вы можете использовать следующий код:

public static string ToStringEnums(Enum en)
{
    Type type = en.GetType();

    MemberInfo[] memInfo = type.GetMember(en.ToString());
    if (memInfo != null && memInfo.Length > 0)
    {
        object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attrs != null && attrs.Length > 0)
            return ((DescriptionAttribute)attrs[0]).Description;
    }
    return en.ToString();
}

Пример того, когда вы хотите использовать этот метод: Когда ваше значение перечисления является EncryptionProviderType и вы хотите, чтобы enumVar.Tostring() возвращал "Тип поставщика шифрования".

Предварительное условие: все члены перечисления должны применяться с атрибутом [Description("String to be returned by Tostring()")].

Пример перечисления:

enum ExampleEnum
{
    [Description("One is one")]
    ValueOne = 1,
    [Description("Two is two")]
    ValueTow = 2
}

И в вашем классе вы будете использовать его следующим образом:

ExampleEnum enumVar = ExampleEnum.ValueOne;
Console.WriteLine(ToStringEnums(enumVar));

Ответ 6

Одна из проблем с этим трюком заключается в том, что атрибут описания не может быть локализован. Мне нравится техника Sacha Barber, где он создает свою собственную версию атрибута Description, которая будет получать значения из соответствующего менеджера ресурсов.

http://www.codeproject.com/KB/WPF/FriendlyEnums.aspx

Несмотря на то, что статья связана с проблемой, с которой обычно сталкиваются разработчики WPF при привязке к перечислениям, вы можете перейти непосредственно к той части, где он создает LocalizableDescriptionAttribute.

Ответ 7

Некоторые замечательные решения уже опубликованы. Когда я столкнулся с этой проблемой, я хотел пойти в обоих направлениях: преобразовать перечисление в описание и преобразовать строку, соответствующую описанию, в перечисление.

У меня есть два варианта: медленный и быстрый. Оба конвертируют из enum в строку и строку в перечисление. Моя проблема в том, что у меня есть перечисления вроде этого, где некоторые элементы нуждаются в атрибутах, а некоторые нет. Я не хочу добавлять атрибуты к элементам, которые им не нужны. В настоящее время у меня около ста этих итогов:

public enum POS
{   
    CC, //  Coordinating conjunction
    CD, //  Cardinal Number
    DT, //  Determiner
    EX, //  Existential there
    FW, //  Foreign Word
    IN, //  Preposision or subordinating conjunction
    JJ, //  Adjective
    [System.ComponentModel.Description("WP$")]
    WPDollar, //$   Possessive wh-pronoun
    WRB, //     Wh-adverb
    [System.ComponentModel.Description("#")]
    Hash,
    [System.ComponentModel.Description("$")]
    Dollar,
    [System.ComponentModel.Description("''")]
    DoubleTick,
    [System.ComponentModel.Description("(")]
    LeftParenth,
    [System.ComponentModel.Description(")")]
    RightParenth,
    [System.ComponentModel.Description(",")]
    Comma,
    [System.ComponentModel.Description(".")]
    Period,
    [System.ComponentModel.Description(":")]
    Colon,
    [System.ComponentModel.Description("``")]
    DoubleBackTick,
    };

Первый метод борьбы с этим медленный и основан на предложениях, которые я видел здесь и вокруг сети. Он медленный, потому что мы отражаем каждое преобразование:

using System;
using System.Collections.Generic;
namespace CustomExtensions
{

/// <summary>
/// uses extension methods to convert enums with hypens in their names to underscore and other variants
public static class EnumExtensions
{
    /// <summary>
    /// Gets the description string, if available. Otherwise returns the name of the enum field
    /// LthWrapper.POS.Dollar.GetString() yields "$", an impossible control character for enums
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string GetStringSlow(this Enum value)
    {
        Type type = value.GetType();
        string name = Enum.GetName(type, value);
        if (name != null)
        {
            System.Reflection.FieldInfo field = type.GetField(name);
            if (field != null)
            {
                System.ComponentModel.DescriptionAttribute attr =
                       Attribute.GetCustomAttribute(field,
                         typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;
                if (attr != null)
                {
                    //return the description if we have it
                    name = attr.Description; 
                }
            }
        }
        return name;
    }

    /// <summary>
    /// Converts a string to an enum field using the string first; if that fails, tries to find a description
    /// attribute that matches. 
    /// "$".ToEnum<LthWrapper.POS>() yields POS.Dollar
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static T ToEnumSlow<T>(this string value) //, T defaultValue)
    {
        T theEnum = default(T);

        Type enumType = typeof(T);

        //check and see if the value is a non attribute value
        try
        {
            theEnum = (T)Enum.Parse(enumType, value);
        }
        catch (System.ArgumentException e)
        {
            bool found = false;
            foreach (T enumValue in Enum.GetValues(enumType))
            {
                System.Reflection.FieldInfo field = enumType.GetField(enumValue.ToString());

                System.ComponentModel.DescriptionAttribute attr =
                           Attribute.GetCustomAttribute(field,
                             typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;

                if (attr != null && attr.Description.Equals(value))
                {
                    theEnum = enumValue;
                    found = true;
                    break;

                }
            }
            if( !found )
                throw new ArgumentException("Cannot convert " + value + " to " + enumType.ToString());
        }

        return theEnum;
    }
}
}

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

Вместо этого мы можем использовать статический конструктор для заполнения некоторых словарей этой информацией о преобразовании, а затем просто искать эту информацию, когда это необходимо. Очевидно, статические классы (необходимые для методов расширения) могут иметь конструкторы и поля:)

using System;
using System.Collections.Generic;
namespace CustomExtensions
{

/// <summary>
/// uses extension methods to convert enums with hypens in their names to underscore and other variants
/// I'm not sure this is a good idea. While it makes that section of the code much much nicer to maintain, it 
/// also incurs a performance hit via reflection. To circumvent this, I've added a dictionary so all the lookup can be done once at 
/// load time. It requires that all enums involved in this extension are in this assembly.
/// </summary>
public static class EnumExtensions
{
    //To avoid collisions, every Enum type has its own hash table
    private static readonly Dictionary<Type, Dictionary<object,string>> enumToStringDictionary = new Dictionary<Type,Dictionary<object,string>>();
    private static readonly Dictionary<Type, Dictionary<string, object>> stringToEnumDictionary = new Dictionary<Type, Dictionary<string, object>>();

    static EnumExtensions()
    {
        //let collect the enums we care about
        List<Type> enumTypeList = new List<Type>();

        //probe this assembly for all enums
        System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
        Type[] exportedTypes = assembly.GetExportedTypes();

        foreach (Type type in exportedTypes)
        {
            if (type.IsEnum)
                enumTypeList.Add(type);
        }

        //for each enum in our list, populate the appropriate dictionaries
        foreach (Type type in enumTypeList)
        {
            //add dictionaries for this type
            EnumExtensions.enumToStringDictionary.Add(type, new Dictionary<object,string>() );
            EnumExtensions.stringToEnumDictionary.Add(type, new Dictionary<string,object>() );

            Array values = Enum.GetValues(type);

            //its ok to manipulate 'value' as object, since when we convert we're given the type to cast to
            foreach (object value in values)
            {
                System.Reflection.FieldInfo fieldInfo = type.GetField(value.ToString());

                //check for an attribute 
                System.ComponentModel.DescriptionAttribute attribute =
                       Attribute.GetCustomAttribute(fieldInfo,
                         typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;

                //populate our dictionaries
                if (attribute != null)
                {
                    EnumExtensions.enumToStringDictionary[type].Add(value, attribute.Description);
                    EnumExtensions.stringToEnumDictionary[type].Add(attribute.Description, value);
                }
                else
                {
                    EnumExtensions.enumToStringDictionary[type].Add(value, value.ToString());
                    EnumExtensions.stringToEnumDictionary[type].Add(value.ToString(), value);
                }
            }
        }
    }

    public static string GetString(this Enum value)
    {
        Type type = value.GetType();
        string aString = EnumExtensions.enumToStringDictionary[type][value];
        return aString; 
    }

    public static T ToEnum<T>(this string value)
    {
        Type type = typeof(T);
        T theEnum = (T)EnumExtensions.stringToEnumDictionary[type][value];
        return theEnum;
    }
 }
}

Посмотрите, насколько сложны методы преобразования. Единственный недостаток, о котором я могу думать, состоит в том, что для этого требуется, чтобы все конвертированные перечисления находились в текущей сборке. Кроме того, я только беспокоюсь об экспортированных перечислениях, но вы можете изменить это, если хотите.

Вот как вызвать методы

 string x = LthWrapper.POS.Dollar.GetString();
 LthWrapper.POS y = "PRP$".ToEnum<LthWrapper.POS>();

Ответ 8

public enum myEnum
{
         ThisNameWorks, 
         This_Name_can_be_used_instead,

}

Ответ 9

После прочтения многих ресурсов по этой теме, включая StackOverFlow, я обнаружил, что не все решения работают исправно. Ниже мы пытаемся исправить это.

В принципе, мы берем дружественное имя Enum из атрибута DescriptionAttribute, если оно существует.
Если он не используется Мы используем RegEx для определения слов в имени Enum и добавления пробелов.

В следующей версии мы будем использовать другой атрибут, чтобы указать, можем ли мы/должны принимать дружественное имя из локализованного файла ресурсов.

Ниже приведены тестовые примеры. Сообщите, если у вас есть другой тестовый пример, который не проходит.

public static class EnumHelper
{
    public static string ToDescription(Enum value)
    {
        if (value == null)
        {
            return string.Empty;
        }

        if (!Enum.IsDefined(value.GetType(), value))
        {
            return string.Empty;
        }

        FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
        if (fieldInfo != null)
        {
            DescriptionAttribute[] attributes =
                fieldInfo.GetCustomAttributes(typeof (DescriptionAttribute), false) as DescriptionAttribute[];
            if (attributes != null && attributes.Length > 0)
            {
                return attributes[0].Description;
            }
        }

        return StringHelper.ToFriendlyName(value.ToString());
    }
}

public static class StringHelper
{
    public static bool IsNullOrWhiteSpace(string value)
    {
        return value == null || string.IsNullOrEmpty(value.Trim());
    }

    public static string ToFriendlyName(string value)
    {
        if (value == null) return string.Empty;
        if (value.Trim().Length == 0) return string.Empty;

        string result = value;

        result = string.Concat(result.Substring(0, 1).ToUpperInvariant(), result.Substring(1, result.Length - 1));

        const string pattern = @"([A-Z]+(?![a-z])|\d+|[A-Z][a-z]+|(?![A-Z])[a-z]+)+";

        List<string> words = new List<string>();
        Match match = Regex.Match(result, pattern);
        if (match.Success)
        {
            Group group = match.Groups[1];
            foreach (Capture capture in group.Captures)
            {
                words.Add(capture.Value);
            }
        }

        return string.Join(" ", words.ToArray());
    }
}


    [TestMethod]
    public void TestFriendlyName()
    {
        string[][] cases =
            {
                new string[] {null, string.Empty},
                new string[] {string.Empty, string.Empty},
                new string[] {" ", string.Empty}, 
                new string[] {"A", "A"},
                new string[] {"z", "Z"},

                new string[] {"Pascal", "Pascal"},
                new string[] {"camel", "Camel"},

                new string[] {"PascalCase", "Pascal Case"}, 
                new string[] {"ABCPascal", "ABC Pascal"}, 
                new string[] {"PascalABC", "Pascal ABC"}, 
                new string[] {"Pascal123", "Pascal 123"}, 
                new string[] {"Pascal123ABC", "Pascal 123 ABC"}, 
                new string[] {"PascalABC123", "Pascal ABC 123"}, 
                new string[] {"123Pascal", "123 Pascal"}, 
                new string[] {"123ABCPascal", "123 ABC Pascal"}, 
                new string[] {"ABC123Pascal", "ABC 123 Pascal"}, 

                new string[] {"camelCase", "Camel Case"}, 
                new string[] {"camelABC", "Camel ABC"}, 
                new string[] {"camel123", "Camel 123"}, 
            };

        foreach (string[] givens in cases)
        {
            string input = givens[0];
            string expected = givens[1];
            string output = StringHelper.ToFriendlyName(input);

            Assert.AreEqual(expected, output);
        }
    }
}

Ответ 10

Они выполняют те же правила именования, что и имена переменных. Поэтому они не должны содержать пробелы.

И то, что вы предлагаете, в любом случае будет очень плохой практикой.

Ответ 11

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

Ответ 12

Это ужасная идея, но она работает.

    public enum myEnum
{
    ThisNameWorks,
    ThisNameDoesntWork149141331,// This Name doesn't work
    NeitherDoesThis1849204824// Neither.does.this;
}

class Program
{
    private static unsafe void ChangeString(string original, string replacement)
    {
        if (original.Length < replacement.Length)
            throw new ArgumentException();

        fixed (char* pDst = original)
        fixed (char* pSrc = replacement)
        {
            // Update the length of the original string
            int* lenPtr = (int*)pDst;
            lenPtr[-1] = replacement.Length;

            // Copy the characters
            for (int i = 0; i < replacement.Length; i++)
                pDst[i] = pSrc[i];
        }
    }

    public static unsafe void Initialize()
    {
        ChangeString(myEnum.ThisNameDoesntWork149141331.ToString(), "This Name doesn't work");
        ChangeString(myEnum.NeitherDoesThis1849204824.ToString(), "Neither.does.this");
    }

    static void Main(string[] args)
    {
        Console.WriteLine(myEnum.ThisNameWorks);
        Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
        Console.WriteLine(myEnum.NeitherDoesThis1849204824);

        Initialize();

        Console.WriteLine(myEnum.ThisNameWorks);
        Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
        Console.WriteLine(myEnum.NeitherDoesThis1849204824);
    }

Требования

  • Имена перечислений должны иметь одинаковое количество символов или больше, чем строка, которую вы хотите использовать.

  • Ваши имена переименований не должны повторяться нигде, на всякий случай, если строка интернирует беспорядок вверх.

Почему это плохая идея (несколько причин)

  • Ваши имена перечислений становятся уродливыми из-за требований

  • Он полагается на то, что вы достаточно быстро вызвали метод инициализации

  • Небезопасные указатели

  • Если внутренний формат строки изменяется, например. если поле длины перемещено, вы ввернуты

  • Если Enum.ToString() когда-либо изменено так, что оно возвращает только копию, вы ввернуты

  • Раймонд Чен будет жаловаться на ваше использование недокументированных функций, и как вы ошибаетесь, что команда CLR не могла сделать оптимизацию, чтобы сократить время выполнения на 50% в течение следующей недели .NET.

Ответ 13

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

Вот мое предложение:

Используйте шаблон типа перечисления. Хотя это требует некоторых усилий для реализации, это действительно того стоит.

public class MyEnum
{  
    public static readonly MyEnum Enum1=new MyEnum("This will work",1);
    public static readonly MyEnum Enum2=new MyEnum("This.will.work.either",2);
    public static readonly MyEnum[] All=new []{Enum1,Enum2};
    private MyEnum(string name,int value)
    {
        Name=name;
        Value=value;
    }

    public string Name{get;set;}
    public int Value{get;set;}

    public override string ToString()
    {
        return Name;    
    }
}