141

c#で列挙型の暗黙的な変換を定義することは可能ですか?

これを達成できる何か?

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

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

そうでない場合、なぜですか?

4

14 に答える 14

131

解決策があります。次の点を考慮してください。

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;
        }
    }
}
于 2010-06-01T11:31:17.080 に答える
38

暗黙の変換 (ゼロを除く) を行うことはできず、独自のインスタンス メソッドを作成することもできません。ただし、おそらく独自の拡張メソッドを作成することはできます。

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

ただし、これではあまり効果がありません (明示的なキャストを行う場合と比較して)。

私が見た主なケースの 1 つは、[Flags]ジェネリック (つまりbool IsFlagSet<T>(T value, T flag);メソッド) を介して操作を行う場合です。残念ながら、C# 3.0 はジェネリックの演算子をサポートしていませんが、このようなものを使用してこれを回避できます。これにより、ジェネリックで演算子を完全に使用できるようになります。

于 2008-11-04T12:21:52.800 に答える
25
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";
于 2012-10-15T18:27:49.337 に答える
18

Markの優れたRichEnum汎用ベースクラスを採用しました。

修正

  1. 彼のライブラリからビットが欠落しているために発生するコンパイルの問題の数(特に、リソースに依存する表示名は完全に削除されていませんでした。現在は削除されています)
  2. 初期化は完全ではありませんでした。最初に行ったのが基本クラスから静的な.Valuesプロパティにアクセスすることであった場合、NPEを取得します。これを修正するには、基本クラスに不思議な再帰CRTP)を強制して、CheckInitialized中にTDerivedの静的な構築をちょうど間に合うように強制します。
  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;
        }
    }
}

私がmonoで実行した使用法のサンプル:

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)

注:mono 2.6.7には、mono2.8.2を使用する場合には必要のない追加の明示的なキャストが必要です。

于 2011-03-29T23:31:14.370 に答える
7

メソッドを定義できないため、列挙型で暗黙的な変換を宣言することはできません。C# の暗黙的なキーワードは、'op_' で始まるメソッドにコンパイルされますが、この場合は機能しません。

于 2008-11-04T12:16:54.743 に答える
5

おそらく可能ですが、列挙型ではできません(メソッドを追加することはできません)。独自のクラスに暗黙的な変換を追加して、列挙型をそれに変換できるようにすることができます。

public class MyClass {

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

MyClass m = MyEnum.One;

問題は、なぜですか?

一般に、.Net は、データが失われる可能性のある暗黙の変換を回避します (ユーザーも回避する必要があります)。

于 2008-11-04T12:18:20.093 に答える
3

列挙型のベースを long として定義すると、明示的な変換を実行できます。列挙型にはメソッドを定義できないため、暗黙的な変換を使用できるかどうかはわかりません。

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

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

また、初期化されていない列挙はデフォルトで 0 値または最初の項目になることに注意してください。したがって、上記の状況ではzero = 0、同様に定義するのがおそらく最善です。

于 2008-11-04T12:11:44.333 に答える
-2

列挙型の暗黙的な変換を導入すると、型の安全性が損なわれるため、お勧めしません。なぜそれをしたいのですか?私が見た唯一の使用例は、事前定義されたレイアウトを持つ構造体に列挙型の値を入れたい場合です。ただし、その場合でも、構造体で列挙型を使用して、マーシャラーにこれで何をすべきかを伝えることができます。

于 2008-11-04T12:19:31.353 に答える