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

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

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

Разве не возможно иметь enumс "дружескими именами"?

13 ответов

Решение

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

Вы могли бы использовать 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();

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

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

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

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

Вы можете использовать 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:

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

И в вашем классе вы бы использовали это так:

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

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

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

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

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

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

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's 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>();
public enum myEnum
{
         ThisNameWorks, 
         This_Name_can_be_used_instead,

}

После прочтения многих ресурсов по этой теме, включая 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);
        }
    }
}

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

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

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

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

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

Требования

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

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

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

  1. Ваши имена enum становятся безобразными из-за требований

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

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

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

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

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

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

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

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

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;    
    }
}
Другие вопросы по тегам