2013-07-12 28 views
7

Khi xử lý các giá trị của một enum trên cơ sở từng trường hợp, tốt hơn là sử dụng câu lệnh chuyển đổi hoặc từ điển?xử lý trường hợp enum - tốt hơn để sử dụng chuyển đổi hoặc từ điển?

Tôi nghĩ từ điển sẽ nhanh hơn. Về mặt không gian, nó chiếm một số trong bộ nhớ, nhưng tuyên bố trường hợp cũng sẽ mất một số bộ nhớ chỉ trong bộ nhớ cần thiết cho chương trình riêng của mình. Vì vậy, dòng dưới cùng tôi nghĩ rằng nó luôn luôn tốt hơn để chỉ sử dụng từ điển.

Dưới đây là hai ngôn ngữ này cạnh nhau để so sánh:

Với những enums:

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. 
} 

Dưới đây là cách để làm điều đó với câu lệnh 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 + "?!"); 
    } 
} 

Và dưới đây là cách thực hiện bằng từ điển:

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. 
} 

Từ điển không chỉ có tăng tốc, mà còn có các chuỗi không cần thiết trong mã. Điều này có tốt hơn khi sử dụng từ điển không? Có cách nào tốt hơn cả ba không?

Xin cảm ơn trước.

+4

Bạn đang thực hiện bao nhiêu lần tra cứu này trong một giây? Nếu nó ít hơn một tỷ bajillion, đừng lo lắng về nó. Chỉ cần chọn phương pháp dễ nhất để bạn đọc/duy trì. –

+0

Công tắc nhanh hơn trong thực tế –

Trả lời

7

Thực tế, từ điển sẽ chậm hơn. Có thật không. Chỉ cần viết benchmark đơn giản (Tôi đã thêm ví dụ với chuyển đổi từ điển để mảng):

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. 
    } 
} 

Kết quả:

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 

gì sẽ xảy ra. Hãy xem mã IL đã tạo:

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   

Điều gì sẽ xảy ra? Switch xảy ra. Đối với chuyển đổi số lượng các chuỗi giá trị có thể được tối ưu hóa và được thay thế bằng cách nhảy tới con trỏ từ mảng. Tại sao mảng thực hoạt động nhanh hơn switch - dunno, nó hoạt động nhanh hơn.

Vâng, nếu bạn không làm việc với enums, nhưng với chuỗi không có sự khác biệt thực sự giữa chuyển đổi và từ điển trên số lượng nhỏ các biến thể. Với ngày càng nhiều biến thể từ điển trở nên nhanh hơn.

Chọn gì? Chọn những gì dễ đọc hơn cho bạn và nhóm của bạn. Khi bạn thấy rằng giải pháp của bạn tạo ra vấn đề hiệu suất, bạn nên thay thế từ điển (nếu bạn sử dụng nó) để chuyển đổi hoặc mảng như tôi. Khi chức năng dịch của bạn hiếm khi được gọi thì không cần phải tối ưu hóa nó.

Nói về trường hợp của bạn - để nhận bản dịch, mọi giải pháp đều xấu. Bản dịch phải được lưu trữ trong tài nguyên. Chỉ có một loại FruitType, không có enums khác.

+2

Rất đẹp. Ví dụ là một chút contrived, thay vì enum -> enum khác nó có thể đã được enum -> Hành động. Những gì bạn có nghĩa là bởi "được lưu trữ trong tài nguyên" mặc dù? – user420667

+0

@ user420667 Dòng được bắt đầu bằng "Nói về trường hợp của bạn". Nói về bản dịch. Đối với tất cả các trường hợp khác, phương pháp này không áp dụng được. Giới thiệu về tài nguyên: http://msdn.microsoft.com/en-us/library/9za7fxc7%28v=vs.80%29.aspx –

+0

+1 cho đề xuất tệp tài nguyên – keyboardP

2

Nó thực sự phụ thuộc vào kịch bản của bạn nhưng, thay vào đó, bạn chỉ có thể có thuộc tính chứa văn bản đã dịch.

public enum FruitType 
{ 
    [Description("Otra")] 
    Other, 
    [Description("Manzana")] 
    Apple,    
    [Description("Platano")] 
    Banana,  
    Mango, 
    [Description("Naranja")] 
    Orange 
} 

Sau đó, bạn có thể có một phương pháp để đọc các mô tả

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 
} 

Vì vậy, bạn có thể gọi nó như thế này

string translated = GetTranslation(FruitType.Apple); 

Vì nó sử dụng phản ánh, điều này có khả năng là các ít hiệu quả nhất nhưng có thể dễ dàng hơn để duy trì tùy thuộc vào tình huống của bạn và, như Chris đã đề cập trong các nhận xét, có thể không có bất kỳ tác động đáng chú ý nào tùy thuộc vào tần suất nó được gọi. Bạn có thể hoán đổi Description cho thuộc tính tùy chỉnh của khóa học. Chỉ một tùy chọn khác để bạn xem xét :)

+0

Không, ý tưởng tồi. Có các mô tả khác nhau cho các ngôn ngữ khác nhau –

+0

@nsinreal - Enums có thể có nhiều thuộc tính ('DescriptionAttribute' không có nhưng không có lý do tại sao thuộc tính tùy chỉnh với' AllowMultiple' được đặt thành 'true' không thể hoạt động). Phải thừa nhận rằng nó có thể không phải là biểu diễn (mặc dù cho dù nó đáng chú ý cần phải được kiểm tra) nhưng nó có nghĩa là chỉ có một nguồn cập nhật cần thiết. – keyboardP

+0

Đó là ý tưởng tồi. Bản dịch phải nằm trong tài nguyên chứ không phải trong mã. –

2

Vì bản dịch là một đối một hoặc một đối tượng, tại sao không gán ID cho mỗi từ. Sau đó đúc từ một enum khác

Vì vậy, xác định sự đếm của bạn như

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 
} 

sau đó xác định phương pháp chuyển đổi của bạn như

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; 
    } 
} 
+1

Tôi đoán trong trường hợp cụ thể này, điều này có lẽ sẽ là một cách tiếp cận tốt. Tuy nhiên, và có lẽ tôi nên đặt câu hỏi này vào câu hỏi, nhưng có thể có tiếng Tây Ban Nha không có ý nghĩa bằng tiếng Anh, trong trường hợp đó, một hoặc một người không phải là không có. – user420667

2

Câu hỏi này dường như được tìm kiếm các phương pháp nhanh nhất lấy một mục được ánh xạ tới hằng số Enum.

Loại cơ bản cho hầu hết tất cả các loại Enum, không phải là các trường bit (tức là không được khai báo là [Flags]), là số nguyên có dấu 32 bit. Có lý do hiệu suất âm thanh cho việc này. Lý do thực sự duy nhất để sử dụng điều gì đó khác biệt là nếu bạn hoàn toàn phải giảm thiểu mức sử dụng bộ nhớ. Các trường bit là một vấn đề khác nhưng chúng tôi không quan tâm đến chúng ở đây.

Trong trường hợp điển hình này, bản đồ mảng là lý tưởng (và thường nhanh hơn switch). Dưới đây là một số mã chung chung là súc tích và được tối ưu hóa để truy xuất. Thật không may, do những hạn chế của các ràng buộc chung của .NET, một vài hacks được yêu cầu (chẳng hạn như phải truyền một delegate đúc vào constructor instance).

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."); 
     } 
    } 
} 

Sau đó bạn có thể chỉ cần khởi ánh xạ sử dụng một giao diện thông thạo đẹp:

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

Và dễ dàng lấy lại giá trị ánh xạ:

ValueType value = mapper[EnumType.Constant3]; 

Việc lắp ráp x86 (tạo ra bằng cách sử dụng Visual Studio Trình biên dịch 2013) cho phương thức truy xuất là tối thiểu:

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 
Các vấn đề liên quan