C# 在C中使用枚举作为数组索引#
我想做与中相同的事情,即:C# 在C中使用枚举作为数组索引#,c#,indexing,enums,C#,Indexing,Enums,我想做与中相同的事情,即: enum DaysOfTheWeek {Sunday=0, Monday, Tuesday...}; string[] message_array = new string[number_of_items_at_enum]; ... Console.Write(custom_array[(int)DaysOfTheWeek.Sunday]); 然而,我更希望有一些集成的东西,而不是编写这种容易出错的代码。C#中是否有一个内置模块可以做到这一点?如果枚举项的值是连
enum DaysOfTheWeek {Sunday=0, Monday, Tuesday...};
string[] message_array = new string[number_of_items_at_enum];
...
Console.Write(custom_array[(int)DaysOfTheWeek.Sunday]);
然而,我更希望有一些集成的东西,而不是编写这种容易出错的代码。C#中是否有一个内置模块可以做到这一点?如果枚举项的值是连续的,那么数组方法工作得很好。但是,在任何情况下,您都可以使用
字典(顺便说一句,它的性能较差)。您可以创建一个类或结构来完成这项工作
编辑
由于没有更好的地方来表达这一点,我在下面发布了一个Caster类的通用版本。不幸的是,它依赖于运行时检查来将TKey强制作为枚举
public enum DayOfWeek
{
Weekend,
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
public class TypeNotSupportedException : ApplicationException
{
public TypeNotSupportedException(Type type)
: base(string.Format("The type \"{0}\" is not supported in this context.", type.Name))
{
}
}
public class CannotBeIndexerException : ApplicationException
{
public CannotBeIndexerException(Type enumUnderlyingType, Type indexerType)
: base(
string.Format("The base type of the enum (\"{0}\") cannot be safely cast to \"{1}\".",
enumUnderlyingType.Name, indexerType)
)
{
}
}
public class Caster<TKey, TValue>
{
private readonly Type baseEnumType;
public Caster()
{
baseEnumType = typeof(TKey);
if (!baseEnumType.IsEnum)
throw new TypeNotSupportedException(baseEnumType);
}
public Caster(TValue[] data)
: this()
{
Data = data;
}
public TValue this[TKey key]
{
get
{
var enumUnderlyingType = Enum.GetUnderlyingType(baseEnumType);
var intType = typeof(int);
if (!enumUnderlyingType.IsAssignableFrom(intType))
throw new CannotBeIndexerException(enumUnderlyingType, intType);
var index = (int) Enum.Parse(baseEnumType, key.ToString());
return Data[index];
}
}
public TValue[] Data { get; set; }
public static implicit operator TValue[](Caster<TKey, TValue> caster)
{
return caster.Data;
}
public static implicit operator Caster<TKey, TValue>(TValue[] data)
{
return new Caster<TKey, TValue>(data);
}
}
// declaring and using it.
Caster<DayOfWeek, string> messageArray =
new[]
{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
Console.WriteLine(messageArray[DayOfWeek.Sunday]);
Console.WriteLine(messageArray[DayOfWeek.Monday]);
Console.WriteLine(messageArray[DayOfWeek.Tuesday]);
Console.WriteLine(messageArray[DayOfWeek.Wednesday]);
Console.WriteLine(messageArray[DayOfWeek.Thursday]);
Console.WriteLine(messageArray[DayOfWeek.Friday]);
Console.WriteLine(messageArray[DayOfWeek.Saturday]);
public enum DayOfWeek
{
周末,,
星期日=0,
星期一,
星期二,
星期三,
星期四,
星期五,
星期六
}
公共类TypeNotSupportedException:ApplicationException
{
公共类型不支持异常(类型类型)
:base(string.Format(“此上下文中不支持类型\“{0}\”,type.Name))
{
}
}
公共类不能索引Exception:ApplicationException
{
公共CannotBeIndexerException(类型EnumUnderlineType,类型indexerType)
:基本(
Format(“枚举的基类型(\“{0}\”)不能安全地强制转换为\“{1}\”,
EnumUnderlineType.Name,indexerType)
)
{
}
}
公共级连铸机
{
私有只读类型baseEnumType;
公共播音员()
{
baseEnumType=typeof(TKey);
如果(!baseEnumType.IsEnum)
抛出新类型NotSupportedException(baseEnumType);
}
公共广播(TValue[]数据)
:此()
{
数据=数据;
}
公共TValue此[TKey]
{
得到
{
var enumUnderlyngType=Enum.GetUnderlyngType(baseEnumType);
var intType=typeof(int);
如果(!EnumUnderlineType.IsAssignableFrom(intType))
抛出新的CannotBeIndexerException(EnumUnderlineType,intType);
var index=(int)Enum.Parse(baseEnumType,key.ToString());
返回数据[索引];
}
}
公共TValue[]数据{get;set;}
公共静态隐式运算符TValue[](Caster-Caster)
{
返回主销数据;
}
公共静态隐式运算符Caster(TValue[]数据)
{
返回新的主销后倾角(数据);
}
}
//声明和使用它。
Caster消息数组=
新[]
{
“星期日”,
“星期一”,
“星期二”,
“星期三”,
“星期四”,
“星期五”,
“星期六”
};
WriteLine(messageArray[DayOfWeek.Sunday]);
WriteLine(messageArray[DayOfWeek.Monday]);
WriteLine(messageArray[DayOfWeek.周二]);
WriteLine(messageArray[DayOfWeek.周三]);
WriteLine(messageArray[DayOfWeek.周四]);
WriteLine(messageArray[DayOfWeek.Friday]);
WriteLine(messageArray[DayOfWeek.Saturday]);
给你:
string[] message_array = Enum.GetNames(typeof(DaysOfTheWeek));
如果您确实需要长度,那么只需在结果上选择.length:)
您可以通过以下方式获取值:
string[] message_array = Enum.GetValues(typeof(DaysOfTheWeek));
始终可以执行一些额外的映射,以一致和定义的方式获取枚举值的数组索引:
int ArrayIndexFromDaysOfTheWeekEnum(DaysOfWeek day)
{
switch (day)
{
case DaysOfWeek.Sunday: return 0;
case DaysOfWeek.Monday: return 1;
...
default: throw ...;
}
}
尽可能具体。有一天,有人会修改您的枚举,代码将失败,因为该枚举的值被(错误地)用作数组索引。压缩形式的枚举用作索引并将任何类型分配给字典
而且是强类型的。在这种情况下,将返回浮点值,但值可能是具有属性和方法等的复杂类实例:
enum opacityLevel { Min, Default, Max }
private static readonly Dictionary<opacityLevel, float> _oLevels = new Dictionary<opacityLevel, float>
{
{ opacityLevel.Max, 40.0 },
{ opacityLevel.Default, 50.0 },
{ opacityLevel.Min, 100.0 }
};
//Access float value like this
var x = _oLevels[opacitylevel.Default];
enum opacityLevel{Min,默认值,Max}
私有静态只读词典_oLevels=新词典
{
{opacityLevel.Max,40.0},
{opacityLevel.Default,50.0},
{opacityLevel.Min,100.0}
};
//像这样访问浮点值
var x=_oLevels[opacitylevel.Default];
如果您只需要一个映射,但又不想产生与字典查找相关的性能开销,那么这可能会起作用:
public class EnumIndexedArray<TKey, T> : IEnumerable<KeyValuePair<TKey, T>> where TKey : struct
{
public EnumIndexedArray()
{
if (!typeof (TKey).IsEnum) throw new InvalidOperationException("Generic type argument is not an Enum");
var size = Convert.ToInt32(Keys.Max()) + 1;
Values = new T[size];
}
protected T[] Values;
public static IEnumerable<TKey> Keys
{
get { return Enum.GetValues(typeof (TKey)).OfType<TKey>(); }
}
public T this[TKey index]
{
get { return Values[Convert.ToInt32(index)]; }
set { Values[Convert.ToInt32(index)] = value; }
}
private IEnumerable<KeyValuePair<TKey, T>> CreateEnumerable()
{
return Keys.Select(key => new KeyValuePair<TKey, T>(key, Values[Convert.ToInt32(key)]));
}
public IEnumerator<KeyValuePair<TKey, T>> GetEnumerator()
{
return CreateEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
显然,此实现由数组支持,因此非连续枚举如下:
enum
{
Ok = 1,
NotOk = 1000000
}
将导致内存过度使用
如果您需要尽可能高的性能,您可能希望使其不那么通用,并释放我为使其编译和工作而必须使用的所有通用枚举处理代码。但我并没有对此进行基准测试,所以也许这没什么大不了的
缓存Keys静态属性也可能有帮助。为便于将来参考,上述问题可总结如下:
我来自Delphi,在Delphi中可以定义如下数组:
type
{$SCOPEDENUMS ON}
TDaysOfTheWeek = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TDaysOfTheWeekStrings = array[TDaysOfTheWeek];
然后可以使用最小值和最大值迭代数组:
for Dow := Min(TDaysOfTheWeek) to Max(TDaysOfTheWeek)
DaysOfTheWeekStrings[Dow] := '';
虽然这是一个精心设计的示例,但当您稍后在代码中处理数组位置时,我可以键入DaysOfTheWeekStrings[TDaysOfTheWeek.Monday]
。这样做的好处是,我应该增加TDaysOfTheWeek
的大小,这样我就不必记住数组的新大小等。。。。。然而,回到C#世界。我发现了这个例子 我意识到这是一个老问题,但有很多人评论说,到目前为止,所有解决方案都有运行时检查,以确保数据类型是枚举。下面是一个完整的解决方案(带有一些示例),其中包含编译时检查(以及我的开发伙伴的一些评论和讨论)
//没有将泛型类参数约束到枚举的好方法。下面的hack在编译时确实有效,
//虽然它很复杂。有关如何使用EnumIndexedArray和ObjEnumIndexedArray这两个类的示例,
//请参见下面的AssetClassArray。或者,例如。
//EConstraint.EnumIndexedArray x=新的EConstraint.EnumIndexedArray();
//看到这个帖子了吗
// http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum/29581813#29581813
//朱莉的回答/评论
enum
{
Ok = 1,
NotOk = 1000000
}
type
{$SCOPEDENUMS ON}
TDaysOfTheWeek = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TDaysOfTheWeekStrings = array[TDaysOfTheWeek];
for Dow := Min(TDaysOfTheWeek) to Max(TDaysOfTheWeek)
DaysOfTheWeekStrings[Dow] := '';
//There is no good way to constrain a generic class parameter to an Enum. The hack below does work at compile time,
// though it is convoluted. For examples of how to use the two classes EnumIndexedArray and ObjEnumIndexedArray,
// see AssetClassArray below. Or, e.g.
// EConstraint.EnumIndexedArray<int, YourEnum> x = new EConstraint.EnumIndexedArray<int, YourEnum>();
// See this post
// http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum/29581813#29581813
// and the answer/comments by Julien Lebosquain
public class EConstraint : HackForCompileTimeConstraintOfTEnumToAnEnum<System.Enum> { }//THIS MUST BE THE ONLY IMPLEMENTATION OF THE ABSTRACT HackForCompileTimeConstraintOfTEnumToAnEnum
public abstract class HackForCompileTimeConstraintOfTEnumToAnEnum<SystemEnum> where SystemEnum : class
{
//For object types T, users should use EnumIndexedObjectArray below.
public class EnumIndexedArray<T, TEnum>
where TEnum : struct, SystemEnum
{
//Needs to be public so that we can easily do things like intIndexedArray.data.sum()
// - just not worth writing up all the equivalent methods, and we can't inherit from T[] and guarantee proper initialization.
//Also, note that we cannot use Length here for initialization, even if Length were defined the same as GetNumEnums up to
// static qualification, because we cannot use a non-static for initialization here.
// Since we want Length to be non-static, in keeping with other definitions of the Length property, we define the separate static
// GetNumEnums, and then define the non-static Length in terms of the actual size of the data array, just for clarity,
// safety and certainty (in case someone does something stupid like resizing data).
public T[] data = new T[GetNumEnums()];
//First, a couple of statics allowing easy use of the enums themselves.
public static TEnum[] GetEnums()
{
return (TEnum[])Enum.GetValues(typeof(TEnum));
}
public TEnum[] getEnums()
{
return GetEnums();
}
//Provide a static method of getting the number of enums. The Length property also returns this, but it is not static and cannot be use in many circumstances.
public static int GetNumEnums()
{
return GetEnums().Length;
}
//This should always return the same as GetNumEnums, but is not static and does it in a way that guarantees consistency with the member array.
public int Length { get { return data.Length; } }
//public int Count { get { return data.Length; } }
public EnumIndexedArray() { }
// [WDS 2015-04-17] Remove. This can be dangerous. Just force people to use EnumIndexedArray(T[] inputArray).
// [DIM 2015-04-18] Actually, if you think about it, EnumIndexedArray(T[] inputArray) is just as dangerous:
// For value types, both are fine. For object types, the latter causes each object in the input array to be referenced twice,
// while the former causes the single object t to be multiply referenced. Two references to each of many is no less dangerous
// than 3 or more references to one. So all of these are dangerous for object types.
// We could remove all these ctors from this base class, and create a separate
// EnumIndexedValueArray<T, TEnum> : EnumIndexedArray<T, TEnum> where T: struct ...
// but then specializing to TEnum = AssetClass would have to be done twice below, once for value types and once
// for object types, with a repetition of all the property definitions. Violating the DRY principle that much
// just to protect against stupid usage, clearly documented as dangerous, is not worth it IMHO.
public EnumIndexedArray(T t)
{
int i = Length;
while (--i >= 0)
{
this[i] = t;
}
}
public EnumIndexedArray(T[] inputArray)
{
if (inputArray.Length > Length)
{
throw new Exception(string.Format("Length of enum-indexed array ({0}) to big. Can't be more than {1}.", inputArray.Length, Length));
}
Array.Copy(inputArray, data, inputArray.Length);
}
public EnumIndexedArray(EnumIndexedArray<T, TEnum> inputArray)
{
Array.Copy(inputArray.data, data, data.Length);
}
//Clean data access
public T this[int ac] { get { return data[ac]; } set { data[ac] = value; } }
public T this[TEnum ac] { get { return data[Convert.ToInt32(ac)]; } set { data[Convert.ToInt32(ac)] = value; } }
}
public class EnumIndexedObjectArray<T, TEnum> : EnumIndexedArray<T, TEnum>
where TEnum : struct, SystemEnum
where T : new()
{
public EnumIndexedObjectArray(bool doInitializeWithNewObjects = true)
{
if (doInitializeWithNewObjects)
{
for (int i = Length; i > 0; this[--i] = new T()) ;
}
}
// The other ctor's are dangerous for object arrays
}
public class EnumIndexedArrayComparator<T, TEnum> : EqualityComparer<EnumIndexedArray<T, TEnum>>
where TEnum : struct, SystemEnum
{
private readonly EqualityComparer<T> elementComparer = EqualityComparer<T>.Default;
public override bool Equals(EnumIndexedArray<T, TEnum> lhs, EnumIndexedArray<T, TEnum> rhs)
{
if (lhs == rhs)
return true;
if (lhs == null || rhs == null)
return false;
//These cases should not be possible because of the way these classes are constructed.
// HOWEVER, the data member is public, so somebody _could_ do something stupid and make
// data=null, or make lhs.data == rhs.data, even though lhs!=rhs (above check)
//On the other hand, these are just optimizations, so it won't be an issue if we reomve them anyway,
// Unless someone does something really dumb like setting .data to null or resizing to an incorrect size,
// in which case things will crash, but any developer who does this deserves to have it crash painfully...
//if (lhs.data == rhs.data)
// return true;
//if (lhs.data == null || rhs.data == null)
// return false;
int i = lhs.Length;
//if (rhs.Length != i)
// return false;
while (--i >= 0)
{
if (!elementComparer.Equals(lhs[i], rhs[i]))
return false;
}
return true;
}
public override int GetHashCode(EnumIndexedArray<T, TEnum> enumIndexedArray)
{
//This doesn't work: for two arrays ar1 and ar2, ar1.GetHashCode() != ar2.GetHashCode() even when ar1[i]==ar2[i] for all i (unless of course they are the exact same array object)
//return engineArray.GetHashCode();
//Code taken from comment by Jon Skeet - of course - in http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array
//31 and 17 are used commonly elsewhere, but maybe because everyone is using Skeet's post.
//On the other hand, this is really not very critical.
unchecked
{
int hash = 17;
int i = enumIndexedArray.Length;
while (--i >= 0)
{
hash = hash * 31 + elementComparer.GetHashCode(enumIndexedArray[i]);
}
return hash;
}
}
}
}
//Because of the above hack, this fails at compile time - as it should. It would, otherwise, only fail at run time.
//public class ThisShouldNotCompile : EConstraint.EnumIndexedArray<int, bool>
//{
//}
//An example
public enum AssetClass { Ir, FxFwd, Cm, Eq, FxOpt, Cr };
public class AssetClassArrayComparator<T> : EConstraint.EnumIndexedArrayComparator<T, AssetClass> { }
public class AssetClassIndexedArray<T> : EConstraint.EnumIndexedArray<T, AssetClass>
{
public AssetClassIndexedArray()
{
}
public AssetClassIndexedArray(T t) : base(t)
{
}
public AssetClassIndexedArray(T[] inputArray) : base(inputArray)
{
}
public AssetClassIndexedArray(EConstraint.EnumIndexedArray<T, AssetClass> inputArray) : base(inputArray)
{
}
public T Cm { get { return this[AssetClass.Cm ]; } set { this[AssetClass.Cm ] = value; } }
public T FxFwd { get { return this[AssetClass.FxFwd]; } set { this[AssetClass.FxFwd] = value; } }
public T Ir { get { return this[AssetClass.Ir ]; } set { this[AssetClass.Ir ] = value; } }
public T Eq { get { return this[AssetClass.Eq ]; } set { this[AssetClass.Eq ] = value; } }
public T FxOpt { get { return this[AssetClass.FxOpt]; } set { this[AssetClass.FxOpt] = value; } }
public T Cr { get { return this[AssetClass.Cr ]; } set { this[AssetClass.Cr ] = value; } }
}
//Inherit from AssetClassArray<T>, not EnumIndexedObjectArray<T, AssetClass>, so we get the benefit of the public access getters and setters above
public class AssetClassIndexedObjectArray<T> : AssetClassIndexedArray<T> where T : new()
{
public AssetClassIndexedObjectArray(bool bInitializeWithNewObjects = true)
{
if (bInitializeWithNewObjects)
{
for (int i = Length; i > 0; this[--i] = new T()) ;
}
}
}
/// <summary>An array indexed by an Enum</summary>
/// <typeparam name="T">Type stored in array</typeparam>
/// <typeparam name="U">Indexer Enum type</typeparam>
public class ArrayByEnum<T,U> : IEnumerable where U : Enum // requires C# 7.3 or later
{
private readonly T[] _array;
private readonly int _lower;
public ArrayByEnum()
{
_lower = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Min());
int upper = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Max());
_array = new T[1 + upper - _lower];
}
public T this[U key]
{
get { return _array[Convert.ToInt32(key) - _lower]; }
set { _array[Convert.ToInt32(key) - _lower] = value; }
}
public IEnumerator GetEnumerator()
{
return Enum.GetValues(typeof(U)).Cast<U>().Select(i => this[i]).GetEnumerator();
}
}
ArrayByEnum<string,MyEnum> myArray = new ArrayByEnum<string,MyEnum>();
myArray[MyEnum.First] = "Hello";
myArray[YourEnum.Other] = "World"; // compiler error