Можем ли мы определить неявные преобразования перечислений в C#?

Можно ли определить неявное преобразование перечислений в с #?

что-то, что могло бы этого достичь?

public enum MyEnum
{
    one = 1, two = 2
}

MyEnum number = MyEnum.one;
long i = number;

Если нет, то почему?

Я тоже хочу это сделать. У нас есть перечисление enum YesNo {Yes, No}, которое можно неявно преобразовать в bool.

Colonel Panic 11.08.2014 17:06

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

crokusek 31.12.2016 10:42

Ссылка больше не действительна - можем ли мы либо удалить ссылку, либо где-нибудь перепостить сайт?

ワイきんぐ 17.07.2019 11:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
132
3
65 857
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

public enum MyEnum : long
{
    one = 1,
    two = 2,
}

MyEnum number = MyEnum.one;
long i = (long)number;

Кроме того, имейте в виду, что неинициализированное перечисление по умолчанию будет иметь значение 0 или первый элемент, поэтому в ситуации выше, вероятно, будет лучше также определить zero = 0.

: long здесь не нужен; явное преобразование нормально работало бы и без него. Единственное допустимое неявное преобразование - ноль.

Marc Gravell 04.11.2008 15:28

Я думал все перечисления, где лонги по умолчанию? Поэтому явные преобразования в лонги уже существуют?

Adam Naylor 04.11.2008 15:40

Нет; перечисление по умолчанию - Int32

Marc Gravell 04.11.2008 15:42

ints по умолчанию, вы можете сделать любой целочисленный тип (byte, short, int, long)

Keith 04.11.2008 15:43

См .: перечисление Foo {A, B, C} Console.WriteLine (Enum.GetUnderlyingType (typeof (Foo)));

Marc Gravell 04.11.2008 15:44

Почему это отмечено как ответ и имеет столько очков? Это НЕ имеет отношения к вопросу OP !!! Он говорит о НЕПРИЯТНОЙ конверсии ... Добавленная стоимость равна нулю.

Mehdi LAMRANI 24.02.2011 18:36

Вопрос уже подразумевает, что явные приведения понятны, вопрос эквивалентен вопросу «Как мне избежать явного приведения?», К которому ЭТО сообщение не относится.

Kit10 31.08.2012 21:52

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

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

public class MyClass {

    public static implicit operator MyClass ( MyEnum input ) {
        //...
    }
}

MyClass m = MyEnum.One;

Возникает вопрос: почему?

В целом .Net избегает (и вам следует тоже) любого неявного преобразования, при котором данные могут быть потеряны.

Введение неявных преобразований для перечисляемых типов нарушит безопасность типов, поэтому я бы не рекомендовал этого делать. Почему вы хотите это сделать? Единственный вариант использования этого, который я видел, - это когда вы хотите поместить значения перечисления в структуру с заранее определенным макетом. Но даже тогда вы можете использовать тип enum в структуре и просто сказать Маршаллеру, что он должен с этим делать.

Я использую неявное преобразование перечислений. Использование SPMetal для создания классов LINQ to SharePoint на нескольких сайтах одного семейства сайтов. Некоторые из моих списков находятся на одном дочернем сайте, другие - на другом. Из-за того, как SPMetal генерирует код, столбцы сайта, используемые в нескольких списках коллекции, могут быть определены в нескольких пространствах имен. Однако мне нужно преобразовать перечисление поля выбора в одном пространстве имен в одно и то же перечисление в другом пространстве имен. Неявное преобразование было бы очень полезно.

Zarepheth 29.08.2013 01:12

Вы не можете выполнять неявные преобразования (кроме нуля), и вы не можете писать свои собственные методы экземпляра - однако вы, вероятно, можете написать свои собственные методы расширения:

public enum MyEnum { A, B, C }
public static class MyEnumExt
{
    public static int Value(this MyEnum foo) { return (int)foo; }
    static void Main()
    {
        MyEnum val = MyEnum.A;
        int i = val.Value();
    }
}

Однако это не дает вам многого (по сравнению с простым явным приведением).

Один из основных случаев, когда я видел, что люди хотят этого, - это манипуляции с [Flags] с помощью дженериков, то есть метода bool IsFlagSet<T>(T value, T flag);. К сожалению, C# 3.0 не поддерживает операторы в универсальных шаблонах, но вы можете обойти это с помощью такие вещи, что делает операторы полностью доступными для универсальных шаблонов.

Да, это было одним из моих самых желанных для C# 4: stackoverflow.com/questions/138367/… и stackoverflow.com/questions/7244

Keith 04.11.2008 15:41

@Keith - хорошо поработал, тогда ;-p Поддержка динамических / операторов не вошла в CTP, но у меня есть готовая к работе испытательная установка, чтобы сравнить два подхода для операторов с динамическим ( vs generics / Expression), когда он туда попадет.

Marc Gravell 04.11.2008 15:45

@Keith - вы можете захотеть дать класс Operator в MiscUtil; Я почти уверен, что он сделает большую часть того, что вы хотите.

Marc Gravell 04.11.2008 15:48
Ответ принят как подходящий

Выход есть. Учтите следующее:

public sealed class AccountStatus
{
    public static readonly AccountStatus Open = new AccountStatus(1);
    public static readonly AccountStatus Closed = new AccountStatus(2);

    public static readonly SortedList<byte, AccountStatus> Values = new SortedList<byte, AccountStatus>();
    private readonly byte Value;

    private AccountStatus(byte value)
    {
        this.Value = value;
        Values.Add(value, this);
    }


    public static implicit operator AccountStatus(byte value)
    {
        return Values[value];
    }

    public static implicit operator byte(AccountStatus value)
    {
        return value.Value;
    }
}

Вышеупомянутое предлагает неявное преобразование:

        AccountStatus openedAccount = 1;            // Works
        byte openedValue = AccountStatus.Open;      // Works

Это немного больше работы, чем объявление обычного перечисления (хотя вы можете реорганизовать некоторые из вышеперечисленных в общий общий базовый класс). Вы можете пойти еще дальше, реализовав в базовом классе IComparable и IEquatable, а также добавив методы для возврата значения DescriptionAttributes, объявленных имен и т. д. И т. Д.

Я написал базовый класс (RichEnum <>) для обработки большей части основной работы, что упрощает указанное выше объявление перечислений до:

public sealed class AccountStatus : RichEnum<byte, AccountStatus>
{
    public static readonly AccountStatus Open = new AccountStatus(1);
    public static readonly AccountStatus Closed = new AccountStatus(2);

    private AccountStatus(byte value) : base (value)
    {
    }

    public static implicit operator AccountStatus(byte value)
    {
        return Convert(value);
    }
}

Базовый класс (RichEnum) указан ниже.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Resources;

namespace Ethica
{
    using Reflection;
    using Text;

    [DebuggerDisplay("{Value} ({Name})")]
    public abstract class RichEnum<TValue, TDerived>
                : IEquatable<TDerived>,
                  IComparable<TDerived>,
                  IComparable, IComparer<TDerived>
        where TValue : struct , IComparable<TValue>, IEquatable<TValue>
        where TDerived : RichEnum<TValue, TDerived>
    {
        #region Backing Fields

        /// <summary>
        /// The value of the enum item
        /// </summary>
        public readonly TValue Value;

        /// <summary>
        /// The public field name, determined from reflection
        /// </summary>
        private string _name;

        /// <summary>
        /// The DescriptionAttribute, if any, linked to the declaring field
        /// </summary>
        private DescriptionAttribute _descriptionAttribute;

        /// <summary>
        /// Reverse lookup to convert values back to local instances
        /// </summary>
        private static SortedList<TValue, TDerived> _values;

        private static bool _isInitialized;


        #endregion

        #region Constructors

        protected RichEnum(TValue value)
        {
            if (_values == null)
                _values = new SortedList<TValue, TDerived>();
            this.Value = value;
            _values.Add(value, (TDerived)this);
        }

        #endregion

        #region Properties

        public string Name
        {
            get
            {
                CheckInitialized();
                return _name;
            }
        }

        public string Description
        {
            get
            {
                CheckInitialized();

                if (_descriptionAttribute != null)
                    return _descriptionAttribute.Description;

                return _name;
            }
        }

        #endregion

        #region Initialization

        private static void CheckInitialized()
        {
            if (!_isInitialized)
            {
                ResourceManager _resources = new ResourceManager(typeof(TDerived).Name, typeof(TDerived).Assembly);

                var fields = typeof(TDerived)
                                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                                .Where(t => t.FieldType == typeof(TDerived));

                foreach (var field in fields)
                {

                    TDerived instance = (TDerived)field.GetValue(null);
                    instance._name = field.Name;
                    instance._descriptionAttribute = field.GetAttribute<DescriptionAttribute>();

                    var displayName = field.Name.ToPhrase();
                }
                _isInitialized = true;
            }
        }

        #endregion

        #region Conversion and Equality

        public static TDerived Convert(TValue value)
        {
            return _values[value];
        }

        public static bool TryConvert(TValue value, out TDerived result)
        {
            return _values.TryGetValue(value, out result);
        }

        public static implicit operator TValue(RichEnum<TValue, TDerived> value)
        {
            return value.Value;
        }

        public static implicit operator RichEnum<TValue, TDerived>(TValue value)
        {
            return _values[value];
        }

        public static implicit operator TDerived(RichEnum<TValue, TDerived> value)
        {
            return value;
        }

        public override string ToString()
        {
            return _name;
        }

        #endregion

        #region IEquatable<TDerived> Members

        public override bool Equals(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.Equals((TValue)obj);

                if (obj is TDerived)
                    return Value.Equals(((TDerived)obj).Value);
            }
            return false;
        }

        bool IEquatable<TDerived>.Equals(TDerived other)
        {
            return Value.Equals(other.Value);
        }


        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        #endregion

        #region IComparable Members

        int IComparable<TDerived>.CompareTo(TDerived other)
        {
            return Value.CompareTo(other.Value);
        }

        int IComparable.CompareTo(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.CompareTo((TValue)obj);

                if (obj is TDerived)
                    return Value.CompareTo(((TDerived)obj).Value);
            }
            return -1;
        }

        int IComparer<TDerived>.Compare(TDerived x, TDerived y)
        {
            return (x == null) ? -1 :
                   (y == null) ? 1 :
                    x.Value.CompareTo(y.Value);
        }

        #endregion

        public static IEnumerable<TDerived> Values
        {
            get
            {
                return _values.Values;
            }
        }

        public static TDerived Parse(string name)
        {
            foreach (TDerived value in _values.Values)
                if (0 == string.Compare(value.Name, name, true) || 0 == string.Compare(value.DisplayName, name, true))
                    return value;

            return null;
        }
    }
}

Исправлена ​​небольшая ошибка в сообщении :-) Это публичный статический неявный оператор AccountStatus (байтовое значение) {return Convert (value); } НЕ возвращать Convert (byte);

Mehdi LAMRANI 24.02.2011 19:03

Я скомпилировал этот базовый класс. Вы не возражаете, если я внесу изменения?

sehe 30.03.2011 02:19

В вашем первом примере (подход, не связанный с базовым классом) SortedList должен быть определен до (выше в коде) экземпляров AccountStatus, иначе конструктор будет жаловаться во время выполнения, что SortedList имеет значение null

musefan 26.01.2012 17:10

Я думал, вы не можете захватить this () в конструкторе ... Я считаю, что это должно вызвать ошибку инициализатора типа, не так ли?

ElfsЯUs 18.07.2013 22:22

Это решение может быть «правильным» в качестве упражнения или проверки чьих-либо навыков программирования, но, пожалуйста, не делайте этого в реальной жизни. Это не только излишество, но и непродуктивно, невозможно обслуживать и чертовски уродливо. Вам не нужно использовать перечисление только ради этого. Вы либо помещаете явное приведение, либо просто пишете статический класс с константными целыми числами.

Trap 08.02.2014 04:56

Разве это не переработанное Java enum?

Agent_L 21.03.2016 18:20

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

Ian Goldby 20.12.2016 17:02

В некоторых местах нельзя использовать элементы static readonly, а использовать только поля const. Например. Параметр ctor Attribute, а также операторы switch, как сказал Ян.

Gabor 14.02.2017 13:10

@IanGoldby Да, вы можете использовать это в операторе switch. Определите статический неявный оператор, который преобразуется в целое число, и экземпляр класса можно использовать в стиле switch(instance). Но опять же, вы не можете использовать его для меток в операторе switch, поскольку они должны быть константами времени компиляции.

Mike de Klerk 10.04.2017 17:39

В проекте, над которым я работаю, у нас есть перечисления с метаданными. Он соответствует записям в базе данных, которые не изменяются динамически и поэтому являются статическими. Например. поле в перечислении имеет связанный порядок. Мы используем их в коде с помощью методов расширения. Например. MyEnum.SomeValue.SequenceOrder(). Nedd для перечислений с переменными метаданными кажется мне недостатком дизайна.

Mike de Klerk 11.04.2017 10:34

@MikedeKlerk Я должен был быть более явным - поскольку вы упомянули, что метки должны быть константами времени компиляции, поэтому я сказал, что вы не можете использовать это в операторе switch. Дело в том, что вы нарушаете инкапсуляцию, если используете метку литерала 2, а не AccountStatus.Closed. Да, вы можете определить константы для использования в метках переключателей и использовать те же константы в своем определении AccountStatus, но я думаю, что это ставит под угрозу исходную цель и смысл существования AccountStatus.

Ian Goldby 11.04.2017 11:17

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

matthias_buehlmann 15.08.2020 19:17

Я адаптировал отличный универсальный базовый класс RichEnum Марка.

Фиксация

  1. ряд проблем компиляции из-за отсутствия битов в его библиотеках (в частности: отображаемые имена, зависящие от ресурсов, не были полностью удалены; теперь они удалены)
  2. инициализация не была идеальной: если бы вы первым делом обратились к статическому свойству .Values ​​из базового класса, вы бы получили NPE. Исправлено это путем принудительного включения базового класса в как ни странно-рекурсивно (CRTP), принудительное статическое построение TDerived как раз вовремя во время CheckInitialized.
  3. наконец, переместил логику CheckInitialized в статический конструктор (чтобы избежать штрафов за проверку каждый раз, условие гонки при многопоточной инициализации; возможно, это была невозможность, решенная моим пунктом 1?)

Престижность Марку за великолепную идею + реализацию, всем вам:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Resources;

namespace NMatrix
{

    [DebuggerDisplay("{Value} ({Name})")]
    public abstract class RichEnum<TValue, TDerived>
                : IEquatable<TDerived>,
                  IComparable<TDerived>,
                  IComparable, IComparer<TDerived>
        where TValue : struct, IComparable<TValue>, IEquatable<TValue>
        where TDerived : RichEnum<TValue, TDerived>
    {
        #region Backing Fields

        /// <summary>
        /// The value of the enum item
        /// </summary>
        public readonly TValue Value;

        /// <summary>
        /// The public field name, determined from reflection
        /// </summary>
        private string _name;

        /// <summary>
        /// The DescriptionAttribute, if any, linked to the declaring field
        /// </summary>
        private DescriptionAttribute _descriptionAttribute;

        /// <summary>
        /// Reverse lookup to convert values back to local instances
        /// </summary>
        private static readonly SortedList<TValue, TDerived> _values = new SortedList<TValue, TDerived>();

        #endregion

        #region Constructors

        protected RichEnum(TValue value)
        {
            this.Value = value;
            _values.Add(value, (TDerived)this);
        }

        #endregion

        #region Properties

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public string Description
        {
            get
            {
                if (_descriptionAttribute != null)
                    return _descriptionAttribute.Description;

                return _name;
            }
        }

        #endregion

        #region Initialization

        static RichEnum()
        {
            var fields = typeof(TDerived)
                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                .Where(t => t.FieldType == typeof(TDerived));

            foreach (var field in fields)
            {
                /*var dummy =*/ field.GetValue(null); // forces static initializer to run for TDerived

                TDerived instance = (TDerived)field.GetValue(null);
                instance._name = field.Name;
                                    instance._descriptionAttribute = field.GetCustomAttributes(true).OfType<DescriptionAttribute>().FirstOrDefault();
            }
        }

        #endregion

        #region Conversion and Equality

        public static TDerived Convert(TValue value)
        {
            return _values[value];
        }

        public static bool TryConvert(TValue value, out TDerived result)
        {
            return _values.TryGetValue(value, out result);
        }

        public static implicit operator TValue(RichEnum<TValue, TDerived> value)
        {
            return value.Value;
        }

        public static implicit operator RichEnum<TValue, TDerived>(TValue value)
        {
            return _values[value];
        }

        public static implicit operator TDerived(RichEnum<TValue, TDerived> value)
        {
            return value;
        }

        public override string ToString()
        {
            return _name;
        }

        #endregion

        #region IEquatable<TDerived> Members

        public override bool Equals(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.Equals((TValue)obj);

                if (obj is TDerived)
                    return Value.Equals(((TDerived)obj).Value);
            }
            return false;
        }

        bool IEquatable<TDerived>.Equals(TDerived other)
        {
            return Value.Equals(other.Value);
        }


        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        #endregion

        #region IComparable Members

        int IComparable<TDerived>.CompareTo(TDerived other)
        {
            return Value.CompareTo(other.Value);
        }

        int IComparable.CompareTo(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.CompareTo((TValue)obj);

                if (obj is TDerived)
                    return Value.CompareTo(((TDerived)obj).Value);
            }
            return -1;
        }

        int IComparer<TDerived>.Compare(TDerived x, TDerived y)
        {
            return (x == null) ? -1 :
                   (y == null) ? 1 :
                    x.Value.CompareTo(y.Value);
        }

        #endregion

        public static IEnumerable<TDerived> Values
        {
            get
            {
                return _values.Values;
            }
        }

        public static TDerived Parse(string name)
        {
            foreach (TDerived value in Values)
                if (0 == string.Compare(value.Name, name, true))
                    return value;

            return null;
        }
    }
}

Пример использования, который я запускал в моно:

using System.ComponentModel;
using System;

namespace NMatrix
{    
    public sealed class MyEnum : RichEnum<int, MyEnum>
    {
        [Description("aap")]  public static readonly MyEnum my_aap   = new MyEnum(63000);
        [Description("noot")] public static readonly MyEnum my_noot  = new MyEnum(63001);
        [Description("mies")] public static readonly MyEnum my_mies  = new MyEnum(63002);

        private MyEnum(int value) : base (value) { } 
        public static implicit operator MyEnum(int value) { return Convert(value); }
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            foreach (var enumvalue in MyEnum.Values)
                Console.WriteLine("MyEnum {0}: {1} ({2})", (int) enumvalue, enumvalue, enumvalue.Description);
        }
    }
}

Производство продукции

[mono] ~/custom/demo @ gmcs test.cs richenum.cs && ./test.exe 
MyEnum 63000: my_aap (aap)
MyEnum 63001: my_noot (noot)
MyEnum 63002: my_mies (mies)

Примечание: для моно 2.6.7 требуется дополнительное явное приведение, которое не требуется при использовании моно 2.8.2 ...

Использование .Single () для получения атрибута описания - не лучшая идея. Если атрибута нет, Single () выдает исключение, а SingleOrDefault () - нет.

kerem 23.01.2012 17:28

@kerem хороший момент, я обновил его (используя FirstOrDefault, чтобы не предполагать, что есть только один атрибут). Считать ли такие вещи `` хорошей идеей '' (или плохой, если на то пошло), конечно, зависит от контекста.

sehe 23.01.2012 19:04

Мне это нравится, но я столкнулся с проблемой: в Windows 7 / .NET 4.5 эта строка TDerived instance = (TDerived)field.GetValue(null); приводит к тому, что instance становится null. Кажется, что среда выполнения Mono должна иметь другой порядок инициализации типов, чем .NET, который позволяет этому работать. Непонятно! Вместо этого мне пришлось переместить этот код в статический метод и вызвать его из инициализатора типа в подклассе.

agentnega 14.03.2014 23:14

@agentnega Спасибо за это добавление. Это может кому-то помочь.

sehe 14.03.2014 23:34

@agentnega У меня такая же проблема в .net 4.5.1. Кажется, что "нарушает" Спецификация C# b / c, он не инициализирует значение перед первым использованием - по крайней мере, не при использовании отражения. Я реализовал обходной путь, который не требует участия подкласса (TDerived). @ sehe, мне следует отредактировать ваш ответ и добавить обходной путь к вашему ответу или опубликовать новый ответ?

BatteryBackupUnit 22.05.2015 12:10

@agentnega, @ sehe тем временем я опубликовал обходной путь (который должен работать в моно и "нормальном" .net) в отдельном ответе, см .: stackoverflow.com/a/30395889/684096

BatteryBackupUnit 22.05.2015 14:53

@BatteryBackupUnit Не стесняйтесь редактировать этот ответ. Я постараюсь вернуться к этому позже

sehe 22.05.2015 14:54
struct PseudoEnum
{
    public const int 
              INPT = 0,
              CTXT = 1,
              OUTP = 2;
};

// ...

var arr = new String[3];

arr[PseudoEnum.CTXT] = "can";
arr[PseudoEnum.INPT] = "use";
arr[PseudoEnum.CTXT] = "as";
arr[PseudoEnum.CTXT] = "array";
arr[PseudoEnum.OUTP] = "index";

но зачем строить?

Konrad 17.01.2019 13:20

На самом деле нет причин. Полагаю, вы могли бы использовать static class. В окончательном коде IL нет аргументов в пользу того или иного преимущества.

Glenn Slayden 18.01.2019 06:40

Я работал над проблемой с ответ при запуске кода в MS .net (не Mono). Для меня проблема возникла в .net 4.5.1, но, похоже, затронуты и другие версии.

Проблема

доступ к public static TDervied MyEnumValue посредством отражения (через FieldInfo.GetValue(null) инициализирует ли нет указанное поле.

Обходной путь

Вместо присвоения имен экземплярам TDerived при статическом инициализаторе RichEnum<TValue, TDerived> это делается лениво при первом доступе к TDerived.Name. Код:

public abstract class RichEnum<TValue, TDerived> : EquatableBase<TDerived>
    where TValue : struct, IComparable<TValue>, IEquatable<TValue>
    where TDerived : RichEnum<TValue, TDerived>
{
    // Enforcing that the field Name (´SomeEnum.SomeEnumValue´) is the same as its 
    // instances ´SomeEnum.Name´ is done by the static initializer of this class.
    // Explanation of initialization sequence:
    // 1. the static initializer of ´RichEnum<TValue, TDerived>´ reflects TDervied and 
    //    creates a list of all ´public static TDervied´ fields:
    //   ´EnumInstanceToNameMapping´
    // 2. the static initializer of ´TDerive´d assigns values to these fields
    // 3. The user is now able to access the values of a field.
    //    Upon first access of ´TDervied.Name´ we search the list 
    //    ´EnumInstanceToNameMapping´ (created at step 1) for the field that holds
    //    ´this´ instance of ´TDerived´.
    //    We then get the Name for ´this´ from the FieldInfo
    private static readonly IReadOnlyCollection<EnumInstanceReflectionInfo> 
                            EnumInstanceToNameMapping = 
        typeof(TDerived)
            .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
            .Where(t => t.FieldType == typeof(TDerived))
            .Select(fieldInfo => new EnumInstanceReflectionInfo(fieldInfo))
            .ToList();

    private static readonly SortedList<TValue, TDerived> Values =
        new SortedList<TValue, TDerived>();

    public readonly TValue Value;

    private readonly Lazy<string> _name;

    protected RichEnum(TValue value)
    {
        Value = value;

        // SortedList doesn't allow duplicates so we don't need to do
        // duplicate checking ourselves
        Values.Add(value, (TDerived)this);

        _name = new Lazy<string>(
                    () => EnumInstanceToNameMapping
                         .First(x => ReferenceEquals(this, x.Instance))
                         .Name);
    }

    public string Name
    {
        get { return _name.Value; }
    }

    public static implicit operator TValue(RichEnum<TValue, TDerived> richEnum)
    {
        return richEnum.Value;
    }

    public static TDerived Convert(TValue value)
    {
        return Values[value];
    }

    protected override bool Equals(TDerived other)
    {
        return Value.Equals(other.Value);
    }

    protected override int ComputeHashCode()
    {
        return Value.GetHashCode();
    }

    private class EnumInstanceReflectionInfo
    {
        private readonly FieldInfo _field;
        private readonly Lazy<TDerived> _instance;

        public EnumInstanceReflectionInfo(FieldInfo field)
        {
            _field = field;
            _instance = new Lazy<TDerived>(() => (TDerived)field.GetValue(null));
        }

        public TDerived Instance
        {
            get { return _instance.Value; }
        }

        public string Name { get { return _field.Name; } }
    }
}

который - в моем случае - основан на EquatableBase<T>:

public abstract class EquatableBase<T>
    where T : class 
{
    public override bool Equals(object obj)
    {
        if (this == obj)
        {
            return true;
        }

        T other = obj as T;
        if (other == null)
        {
            return false;
        }

        return Equals(other);
    }

    protected abstract bool Equals(T other);

    public override int GetHashCode()
    {
        unchecked
        {
            return ComputeHashCode();
        }
    }

    protected abstract int ComputeHashCode();
}

Примечание

Приведенный выше код не включает все функции исходного ответа отметка!

Спасибо

Спасибо отметка за предоставление своей реализации RichEnum и спасибо sehe за предоставление некоторых улучшений!

Я нашел еще более простое решение, взятое отсюда. https://codereview.stackexchange.com/questions/7566/enum-vs-int-wrapper-struct Я вставил приведенный ниже код из этой ссылки на случай, если он не сработает в будущем.

struct Day
{
    readonly int day;

    public static readonly Day Monday = 0;
    public static readonly Day Tuesday = 1;
    public static readonly Day Wednesday = 2;
    public static readonly Day Thursday = 3;
    public static readonly Day Friday = 4;
    public static readonly Day Saturday = 5;
    public static readonly Day Sunday = 6;

    private Day(int day)
    {
        this.day = day;
    }

    public static implicit operator int(Day value)
    {
        return value.day;
    }

    public static implicit operator Day(int value)
    {
        return new Day(value);
    }
}

перечисления в значительной степени бесполезны для меня из-за этого OP.

В итоге я все время занимаюсь фотографиями:

простое решение

Классическим примером проблемы является набор VirtualKey для обнаружения нажатий клавиш.

enum VKeys : ushort
{
a = 1,
b = 2,
c = 3
}
// the goal is to index the array using predefined constants
int[] array = new int[500];
var x = array[VKeys.VK_LSHIFT]; 

проблема здесь в том, что вы не можете проиндексировать массив с помощью enum, потому что он не может неявно преобразовать enum в ushort (хотя мы даже основали перечисление на ushort)

в этом конкретном контексте перечисления устарели следующей структурой данных . . . .

public static class VKeys
{
public const ushort
a = 1,
b = 2, 
c = 3;
}

Я создал эту утилиту, чтобы помочь мне преобразовать Enum в PrimitiveEnum и PrimitiveEnum в byte, sbyte, short, ushort, int, uint, long, or ulong.

Таким образом, это технически преобразует любое перечисление в любое его примитивное значение.

public enum MyEnum
{
    one = 1, two = 2
}

PrimitiveEnum number = MyEnum.one;
long i = number;

См. Фиксацию в https://github.com/McKabue/McKabue.Extentions.Utility/blob/master/src/McKabue.Extentions.Utility/Enums/PrimitiveEnum.cs

using System;

namespace McKabue.Extentions.Utility.Enums
{
    /// <summary>
    /// <see href = "https://stackoverflow.com/q/261663/3563013">
    /// Can we define implicit conversions of enums in c#?
    /// </see>
    /// </summary>
    public struct PrimitiveEnum
    {
        private Enum _enum;

        public PrimitiveEnum(Enum _enum)
        {
            this._enum = _enum;
        }

        public Enum Enum => _enum;


        public static implicit operator PrimitiveEnum(Enum _enum)
        {
            return new PrimitiveEnum(_enum);
        }

        public static implicit operator Enum(PrimitiveEnum primitiveEnum)
        {
            return primitiveEnum.Enum;
        }

        public static implicit operator byte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToByte(primitiveEnum.Enum);
        }

        public static implicit operator sbyte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToSByte(primitiveEnum.Enum);
        }

        public static implicit operator short(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt16(primitiveEnum.Enum);
        }

        public static implicit operator ushort(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt16(primitiveEnum.Enum);
        }

        public static implicit operator int(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt32(primitiveEnum.Enum);
        }

        public static implicit operator uint(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt32(primitiveEnum.Enum);
        }

        public static implicit operator long(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt64(primitiveEnum.Enum);
        }

        public static implicit operator ulong(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt64(primitiveEnum.Enum);
        }
    }
}

+1 У меня есть игровой фреймворк со многими вещами, идентифицированными uint, для которых сама игра обычно делает enum, но фреймворк ничего не знает. Наличие (uint) при вызове фреймворка было проблемой. Ваша идея задом наперед работает отлично. Вместо struct, хранящего Enum, у меня есть struct IdNumber, который хранит uint, но неявно конвертирует из Enum, которые использует игра. Вместо того, чтобы вводить параметры фреймворка как uint, я могу ввести их как IdNumber, и фреймворк сможет эффективно передавать их внутри, даже выполняя с ними комплексные операции.

Kevin 21.03.2020 11:44

@BatteryBackupUnit Эй, это звучит как классное решение, но не могли бы вы объяснить эту часть здесь?

Поскольку я получаю с .NET 4.7.2 "InvalidCastException", к сожалению: /

 _name = new Lazy<string>(
                () => EnumInstanceToNameMapping
                     .First(x => ReferenceEquals(this, x.Instance))
                     .Name);

Я не знаю почему, я создал производный тип RichEnum и инициализировал, как все, что вы делали в примере, но я получаю это исключение annyoingg ..

Был бы рад некоторой помощи в этом, так как мне очень нравится этот подход.

Другие вопросы по теме