根据具体情况处理枚举值时,使用switch语句或字典会更好吗?
我认为字典会更快。就空间而言,它占用了一些内存,但case语句也会占用程序本身所需的内存中的一些内存。所以底线我认为只使用字典总是更好。
以下两个实现并列进行比较:
鉴于这些枚举:
enum FruitType
{
Other,
Apple,
Banana,
Mango,
Orange
}
enum SpanishFruitType
{
Otra,
Manzana, // Apple
Naranja, // Orange
Platano, // Banana
Pitaya // Dragon fruit, only grown in Mexico and South American countries, lets say
// let's say they don't have mangos, because I don't remember the word for it.
}
以下是使用switch语句执行此操作的方法:
private static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
{
switch(typeOfFruit)
{
case FruitType.Apple:
return SpanishFruitType.Manzana;
case FruitType.Banana:
return SpanishFruitType.Platano;
case FruitType.Orange:
return SpanishFruitType.Naranja;
case FruitType.Mango:
case FruitType.Other:
return SpanishFruitType.Otra;
default:
throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
}
}
以下是字典的完成方式:
private static Dictionary<FruitType, SpanishFruitType> EnglishToSpanishFruit = new Dictionary<FruitType, SpanishFruitType>()
{
{FruitType.Apple, SpanishFruitType.Manzana}
,{FruitType.Banana, SpanishFruitType.Platano}
,{FruitType.Mango, SpanishFruitType.Otra}
,{FruitType.Orange, SpanishFruitType.Naranja}
,{FruitType.Other, SpanishFruitType.Otra}
};
private static SpanishFruitType GetSpanishEquivalentWithDictionary(FruitType typeOfFruit)
{
return EnglishToSpanishFruit[typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}
字典不仅具有提升速度,而且代码中的字符串也更少。那么使用字典总是更好吗?还有更好的方法吗?
提前致谢。
答案 0 :(得分:8)
事实上,字典速度较慢。真。只需编写简单的基准测试(我添加了将字典转换为数组的示例):
void Main()
{
for (int itFac = 0; itFac < 7; itFac++ ) {
var iterations = 100;
iterations *= (int)Math.Pow(10, itFac);
Console.WriteLine("Iterations: {0}", iterations);
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalentWithArray((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Array time: {0}", timer.Elapsed);
}
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalent((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Switch time : {0}", timer.Elapsed);
}
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalentWithDictionary((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Dictionary time: {0}", timer.Elapsed);
}
Console.WriteLine();
}
}
class Fruits {
public enum FruitType
{
Other,
Apple,
Banana,
Mango,
Orange
}
public enum SpanishFruitType
{
Otra,
Manzana, // Apple
Naranja, // Orange
Platano, // Banana
// let's say they don't have mangos, because I don't remember the word for it.
}
public static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
{
switch(typeOfFruit)
{
case FruitType.Apple:
return SpanishFruitType.Manzana;
case FruitType.Banana:
return SpanishFruitType.Platano;
case FruitType.Orange:
return SpanishFruitType.Naranja;
case FruitType.Mango:
case FruitType.Other:
return SpanishFruitType.Otra;
default:
throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
}
}
public static SpanishFruitType GetSpanishEquivalent(string typeOfFruit)
{
switch(typeOfFruit)
{
case "apple":
return SpanishFruitType.Manzana;
case "banana":
return SpanishFruitType.Platano;
case "orange":
return SpanishFruitType.Naranja;
case "mango":
case "other":
return SpanishFruitType.Otra;
default:
throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
}
}
public static Dictionary<FruitType, SpanishFruitType> EnglishToSpanishFruit = new Dictionary<FruitType, SpanishFruitType>()
{
{FruitType.Apple, SpanishFruitType.Manzana}
,{FruitType.Banana, SpanishFruitType.Platano}
,{FruitType.Mango, SpanishFruitType.Otra}
,{FruitType.Orange, SpanishFruitType.Naranja}
,{FruitType.Other, SpanishFruitType.Otra}
};
public static SpanishFruitType GetSpanishEquivalentWithDictionary(FruitType typeOfFruit)
{
return EnglishToSpanishFruit[typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}
public static SpanishFruitType[] EnglishToSpanishFruitArray;
static Fruits() {
EnglishToSpanishFruitArray = new SpanishFruitType[EnglishToSpanishFruit.Select(p => (int)p.Key).Max() + 1];
foreach (var pair in EnglishToSpanishFruit)
EnglishToSpanishFruitArray[(int)pair.Key] = pair.Value;
}
public static SpanishFruitType GetSpanishEquivalentWithArray(FruitType typeOfFruit)
{
return EnglishToSpanishFruitArray[(int)typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}
}
结果:
Iterations: 100
Array time : 00:00:00.0108628
Switch time : 00:00:00.0002204
Dictionary time: 00:00:00.0008475
Iterations: 1000
Array time : 00:00:00.0000410
Switch time : 00:00:00.0000472
Dictionary time: 00:00:00.0004556
Iterations: 10000
Array time : 00:00:00.0006095
Switch time : 00:00:00.0011230
Dictionary time: 00:00:00.0074769
Iterations: 100000
Array time : 00:00:00.0043019
Switch time : 00:00:00.0047117
Dictionary time: 00:00:00.0611122
Iterations: 1000000
Array time : 00:00:00.0468998
Switch time : 00:00:00.0520848
Dictionary time: 00:00:00.5861588
Iterations: 10000000
Array time : 00:00:00.4268453
Switch time : 00:00:00.5002004
Dictionary time: 00:00:07.5352484
Iterations: 100000000
Array time : 00:00:04.1720282
Switch time : 00:00:04.9347176
Dictionary time: 00:00:56.0107932
会发生什么。让我们看一下生成的IL代码:
Fruits.GetSpanishEquivalent:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: switch (IL_002B, IL_001F, IL_0023, IL_002B, IL_0027)
IL_001D: br.s IL_002F
IL_001F: ldc.i4.1
IL_0020: stloc.0
IL_0021: br.s IL_004A
IL_0023: ldc.i4.3
IL_0024: stloc.0
IL_0025: br.s IL_004A
IL_0027: ldc.i4.2
IL_0028: stloc.0
IL_0029: br.s IL_004A
IL_002B: ldc.i4.0
IL_002C: stloc.0
IL_002D: br.s IL_004A
IL_002F: ldstr "what kind of fruit is "
IL_0034: ldarg.0
IL_0035: box UserQuery+Fruits.FruitType
IL_003A: ldstr "?!"
IL_003F: call System.String.Concat
IL_0044: newobj System.Exception..ctor
IL_0049: throw
IL_004A: ldloc.0
IL_004B: ret
会发生什么? Switch发生了。对于有序的数值,可以优化切换,并通过从数组跳转到指针来替换。为什么真正的数组比switch更快 - dunno,它只是工作得更快。
好吧,如果你不使用枚举,但是使用字符串,那么在少数变体上开关和字典之间没有真正的区别。随着越来越多的变体字典变得更快。
选择什么?选择更适合您和您的团队的内容。当您看到您的解决方案产生性能问题时,您应该将Dictionary(如果使用它)替换为像我这样的切换或数组。当您很少调用您的翻译功能时,无需对其进行优化。
谈论你的案例 - 要获得翻译,所有解决方案都很糟糕。翻译必须存储在资源中。必须只有一个FruitType,没有其他枚举。
答案 1 :(得分:2)
这实际上取决于您的场景,但作为替代方案,您可以只拥有包含翻译文本的属性。
public enum FruitType
{
[Description("Otra")]
Other,
[Description("Manzana")]
Apple,
[Description("Platano")]
Banana,
Mango,
[Description("Naranja")]
Orange
}
然后你可以有一个方法来阅读描述
public static string GetTranslation(FruitType fruit)
{
var mi = typeof(FruitType).GetMember(fruit.ToString());
var attr = mi[0].GetCustomAttributes(typeof(DescriptionAttribute),false);
if (attr.Count() > 0)
return ((DescriptionAttribute)attr[0]).Description;
else
return fruit.ToString(); //if no description added, return the original fruit
}
所以你可以这样称呼它
string translated = GetTranslation(FruitType.Apple);
由于它使用反射,这可能是效率最低的,但可能更容易维护,具体取决于您的情况,正如Chris在评论中提到的,可能没有任何明显的影响,具体取决于它的调用频率。您当然可以将Description
换成自定义属性。您可以考虑另一种选择:)
答案 2 :(得分:2)
由于翻译是一对一或一对一,为什么不为每个单词分配ID。然后从一个枚举投射到另一个
因此将您的枚举定义为
enum FruitType
{
Other = 0,
Apple = 1,
Banana = 2,
Mango = 3,
Orange = 4
}
enum SpanishFruitType
{
Otra = 0,
Manzana = 1, // Apple
Platano = 2, // Banana
Naranja = 4, // Orange
}
然后将转换方法定义为
private static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
{
//'translate' with the word's ID.
//If there is no translation, the enum would be undefined
SpanishFruitType translation = (SpanishFruitType)(int)typeOfFruit;
//Check if the translation is defined
if (Enum.IsDefined(typeof(SpanishFruitType), translation))
{
return translation;
}
else
{
return SpanishFruitType.Otra;
}
}
答案 3 :(得分:2)
这个问题似乎是寻找检索映射到Enum
常量的项目的最快方法。
几乎所有Enum
类型的基础类型(不是位字段(即未声明为[Flags]
))是32位有符号整数。这有合理的性能原因。使用不同内容的唯一真正原因是,如果您绝对必须最小化内存使用量。比特字段是另一回事,但我们在这里并不关心它们。
在这种典型情况下,阵列图是理想的(并且通常比switch
更快)。这是一些简洁和优化的通用代码,用于检索。不幸的是,由于.NET泛型约束的限制,需要一些hacks(例如必须将转换委托传递给实例构造函数)。
using System;
using System.Runtime.CompilerServices;
namespace DEMO
{
public sealed class EnumMapper<TKey, TValue> where TKey : struct, IConvertible
{
private struct FlaggedValue<T>
{
public bool flag;
public T value;
}
private static readonly int size;
private readonly Func<TKey, int> func;
private FlaggedValue<TValue>[] flaggedValues;
public TValue this[TKey key]
{
get
{
int index = this.func.Invoke(key);
FlaggedValue<TValue> flaggedValue = this.flaggedValues[index];
if (flaggedValue.flag == false)
{
EnumMapper<TKey, TValue>.ThrowNoMappingException(); // Don't want the exception code in the method. Make this callsite as small as possible to promote JIT inlining and squeeze out every last bit of performance.
}
return flaggedValue.value;
}
}
static EnumMapper()
{
Type keyType = typeof(TKey);
if (keyType.IsEnum == false)
{
throw new Exception("The key type [" + keyType.AssemblyQualifiedName + "] is not an enumeration.");
}
Type underlyingType = Enum.GetUnderlyingType(keyType);
if (underlyingType != typeof(int))
{
throw new Exception("The key type's underlying type [" + underlyingType.AssemblyQualifiedName + "] is not a 32-bit signed integer.");
}
var values = (int[])Enum.GetValues(keyType);
int maxValue = 0;
foreach (int value in values)
{
if (value < 0)
{
throw new Exception("The key type has a constant with a negative value.");
}
if (value > maxValue)
{
maxValue = value;
}
}
EnumMapper<TKey, TValue>.size = maxValue + 1;
}
public EnumMapper(Func<TKey, int> func)
{
if (func == null)
{
throw new ArgumentNullException("func",
"The func cannot be a null reference.");
}
this.func = func;
this.flaggedValues = new FlaggedValue<TValue>[EnumMapper<TKey, TValue>.size];
}
public static EnumMapper<TKey, TValue> Construct(Func<TKey, int> func)
{
return new EnumMapper<TKey, TValue>(func);
}
public EnumMapper<TKey, TValue> Map(TKey key,
TValue value)
{
int index = this.func.Invoke(key);
FlaggedValue<TValue> flaggedValue;
flaggedValue.flag = true;
flaggedValue.value = value;
this.flaggedValues[index] = flaggedValue;
return this;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNoMappingException()
{
throw new Exception("No mapping exists corresponding to the key.");
}
}
}
然后,您可以使用流畅的界面简单地初始化映射:
var mapper = EnumMapper<EnumType, ValueType>.Construct((x) => (int)x)
.Map(EnumType.Constant1, value1)
.Map(EnumType.Constant2, value2)
.Map(EnumType.Constant3, value3)
.Map(EnumType.Constant4, value4)
.Map(EnumType.Constant5, value5);
轻松检索映射值:
ValueType value = mapper[EnumType.Constant3];
用于检索方法的x86程序集(使用Visual Studio 2013编译器生成)是最小的:
000007FE8E9909B0 push rsi
000007FE8E9909B1 sub rsp,20h
000007FE8E9909B5 mov rsi,rcx
000007FE8E9909B8 mov rax,qword ptr [rsi+8]
000007FE8E9909BC mov rcx,qword ptr [rax+8]
000007FE8E9909C0 call qword ptr [rax+18h] // The casting delegate's callsite is optimised to just two instructions
000007FE8E9909C3 mov rdx,qword ptr [rsi+10h]
000007FE8E9909C7 mov ecx,dword ptr [rdx+8]
000007FE8E9909CA cmp eax,ecx
000007FE8E9909CC jae 000007FE8E9909ED
000007FE8E9909CE movsxd rax,eax
000007FE8E9909D1 lea rax,[rdx+rax*8+10h]
000007FE8E9909D6 movzx edx,byte ptr [rax]
000007FE8E9909D9 mov esi,dword ptr [rax+4]
000007FE8E9909DC test dl,dl
000007FE8E9909DE jne 000007FE8E9909E5
000007FE8E9909E0 call 000007FE8E9901B8
000007FE8E9909E5 mov eax,esi
000007FE8E9909E7 add rsp,20h
000007FE8E9909EB pop rsi
000007FE8E9909EC ret
000007FE8E9909ED call 000007FEEE411A08
000007FE8E9909F2 int 3