C#에서 열거형의 암시적 변환을 정의할 수 있나요?
-
06-07-2019 - |
문제
C#에서 열거형의 암시적 변환을 정의할 수 있습니까?
이것을 달성할 수 있는 뭔가?
public enum MyEnum
{
one = 1, two = 2
}
MyEnum number = MyEnum.one;
long i = number;
그렇지 않다면 왜 안 됩니까?
이에 대한 추가 논의와 아이디어를 위해 현재 이 문제를 처리하는 방법을 추적했습니다. C# 열거형 개선
해결책
해결책이 있습니다. 다음을 고려하세요:
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[byte];
}
public static implicit operator byte(AccountStatus value)
{
return value.Value;
}
}
위의 내용은 암시 적 변환을 제공합니다.
AccountStatus openedAccount = 1; // Works
byte openedValue = AccountStatus.Open; // Works
이것은 일반적인 열거를 선언하는 것보다 상당한 작업입니다 (위의 일부를 일반적인 일반 기본 클래스로 리팩터링 할 수 있지만). 기본 클래스가 icomparable 및 iquationable을 구현하고 DescriptionAttributes, 선언 된 이름 등의 값을 반환하는 방법을 추가하여 더 나아갈 수 있습니다.
나는 대부분의 grunt 작업을 처리하기 위해 기본 클래스 (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;
}
}
}
다른 팁
당신은 변환을 암시 할 수 없으며 (제로 제외) 자신의 인스턴스 방법을 작성할 수는 없지만 자체 확장 방법을 작성할 수 있습니다.
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]
제네릭을 통한 조작 - 즉 a bool IsFlagSet<T>(T value, T flag);
방법. 불행히도 C# 3.0은 제네릭에서 운영자를 지원하지 않지만 이런 것들, 운영자는 제네릭으로 완전히 사용할 수있게합니다.
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";
나는 Mark의 뛰어난 RichEnum 일반 기본 클래스를 적용했습니다.
고정
- 그의 라이브러리에서 누락된 비트로 인해 여러 가지 컴파일 문제가 발생했습니다(특히:리소스 종속 표시 이름이 완전히 제거되지 않았습니다.그들은 지금)
- 초기화가 완벽하지 않았습니다.가장 먼저 수행한 작업이 기본 클래스에서 정적 .Values 속성에 액세스하는 것이라면 NPE를 얻게 됩니다.기본 클래스를 강제로 실행하여 이 문제를 해결했습니다. 신기하게도 재귀적으로 (CRTP) CheckInitialized 동안 적시에 TDerived의 정적 구성을 강제합니다.
- 마지막으로 CheckInitialized 논리를 정적 생성자로 옮겼습니다(매번 멀티스레드 초기화에 대한 경쟁 조건을 확인해야 하는 불이익을 피하기 위해).아마도 이것은 내 총알 1로 해결된 불가능이었을 것입니다.?)
훌륭한 아이디어와 구현에 대해 Mark에게 찬사를 보냅니다. 여러분 모두에게 감사드립니다.
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)
메모:mono 2.6.7에는 mono 2.8.2를 사용할 때 필요하지 않은 추가 명시적 캐스트가 필요합니다...
당신은 아마 열거 할 수는 있지만 아마도 메소드를 추가 할 수는 없습니다. 열거를 변환 할 수 있도록 자신의 클래스에 암시 적 변환을 추가 할 수 있습니다.
public class MyClass {
public static implicit operator MyClass ( MyEnum input ) {
//...
}
}
MyClass m = MyEnum.One;
문제는 왜 그런가?
일반적으로 .NET은 데이터를 잃을 수있는 암시 적 변환을 피하고 (그리고 당신도해야합니다).
열거의베이스를 오랫동안 정의하면 명시 적 변환을 수행 할 수 있습니다. 열거적 인 변환을 열거로 정의 할 수 없으므로 암시 적 변환을 사용할 수 있는지 모르겠습니다.
public enum MyEnum : long
{
one = 1,
two = 2,
}
MyEnum number = MyEnum.one;
long i = (long)number;
또한, 이에 비해 시작되지 않은 열거는 0 값 또는 첫 번째 항목으로 기본적으로 기본값을 할 것입니다. 따라서 위의 상황에서는 정의하는 것이 가장 좋습니다. zero = 0
또한.
방법을 정의 할 수 없기 때문에 열거 유형에 대한 암시 적 변환을 선언 할 수 없습니다. C# 절대적인 키워드는 'OP_'로 시작하는 메소드로 컴파일 되며이 경우 작동하지 않습니다.
나는 문제를 해결했다 Sehe의 대답 MS .NET (비 모노)에서 코드를 실행할 때. 저에게 특히 문제는 .NET 4.5.1에서 발생했지만 다른 버전도 영향을받는 것 같습니다.
문제
액세스 a 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();
}
메모
위의 코드는 모든 기능을 포함하지 않습니다 표시원래 답변!
감사
여기에서 더 쉬운 솔루션을 찾았습니다 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);
}
}
열거는 이것 때문에 나에게는 크게 쓸모가 없다.
나는 항상 사진 관련을 끝내게됩니다.
전형적인 예제 문제는 키 프레스를 감지하기위한 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];
여기서 문제는 열거적으로 열거를 Ushort로 변환 할 수 없기 때문에 Enum으로 배열을 색인 할 수 없다는 것입니다 (Ushort에 열거를 기반으로하더라도).
이러한 특정 맥락에서, 열거는 다음 데이터를 사용하지 않습니다. . . .
public static class VKeys
{
public const ushort
a = 1,
b = 2,
c = 3;
}
열거 유형에 대한 암시 적 변환을 도입하면 유형의 안전성을 깨뜨릴 수 있으므로 그렇게하는 것이 좋습니다. 왜 그렇게하고 싶습니까? 내가 본 유일한 사용 사례는 열거 값을 사전 정의 된 레이아웃을 가진 구조에 넣고 싶을 때입니다. 그러나 그럼에도 불구하고, 당신은 구조물에서 열거 형식을 사용하고 마샬러에게 그가해야 할 일을 말할 수 있습니다.