【问题标题】:Containskey VS Try CatchContainskey VS Try Catch
【发布时间】:2012-09-07 03:49:16
【问题描述】:

我有一个 Vector2 的 Generated 列表,我必须检查字典以查看它们是否存在,这个函数在每个滴答声中执行。

以这种方式运行最快/更好?

    public static bool exists(Vector2 Position, Dictionary<Vector2, object> ToCheck)
    {
        try
        {
            object Test = ToCheck[Position];
            return (true);
        }
        catch 
        {
            return (false);
        }           
    }

还是我应该坚持规范?

    public static bool exists(Vector2 Position, Dictionary<Vector2, object> ToCheck)
    {
        if (ToCheck.ContainsKey(Position))
        {
            return (true);
        }
        return (false);
    }

感谢您的意见:)

旁注:(此时键的值并不重要,否则我会使用 TryGetValue 而不是 ContainsKey)

【问题讨论】:

  • 你为什么要写第二种方法?您实际上是在用另一个函数调用包装一个函数调用,并且什么也不做。调用者可以直接调用ContainsKey,而不是调用该函数
  • 是的,只需返回ToCheck.ContainsKey(Position)
  • @nawfal - 顺便说一句,Servy 说的更深一些。不要简化那个函数(你可以很好地说明简单的单行内容是什么),根本不要编写那个方法。无论有人在哪里做exists(myPosition, myDictionary),他们都可以简单地拨打标准电话myDictionary.ContainsKey(myPosition。这样任何阅读代码的人都不必去查找这个神秘的exists,它并没有添加任何有用的东西(调用起来并不简单)。
  • 实际上,这个问题引出了一个问题:@Dusty 将如何处理结果?这是“优化级别太低”的一个例子。通常不成功。相反,请查看existsContainsKey 的使用位置(exists 的“调用者”)。如果这些“调用者”中的任何一个对性能至关重要,那么他们是否在ToCheck 上进行了多个方法调用,可以用更少的调用来代替?经典的例子是将ToCheck.ContainsKey( key ) + ... = ToCheck[key] 替换为TryGetValue是有希望获得性能提升的地方。

标签: c# dictionary try-catch containskey


【解决方案1】:

一定要使用ContainsKey检查;异常处理可以add a large overhead

抛出异常会对性能产生负面影响。对于经常失败的代码,您可以使用设计模式来最大程度地减少性能问题。

异常不适用于您可以检查的条件。

我建议阅读 exceptions generally,尤其是 exception handling 上的 MSDN 文档。

【讨论】:

  • 在某些情况下调用你知道的方法会引发异常,就像闭着眼睛开车一样。有更好的方法来发现道路通向何处,而不是等到您撞到或驶过某物。
  • +1。除了错误的代码流结构和巨大的运行时成本之外,异常使调试变得更加困难(假设您已经“抛出时中断”检查异常......这是避免频繁执行的代码段中出现异常的好主意)。
  • 感谢您的信息,帮助很大,我想知道因为我在另一篇文章中读到(我不记得主要思想),有人回答说 try catch 几乎不会影响性能。
  • @Dusty,我一直不得不纠正这种误解。这可能是由于强烈鼓励使用异常作为控制流的其他语言(例如 Python)。但就 .NET(以及 JVM,就此而言)而言,您永远不应该抛出异常,除非它代表您无法恢复的场景。
【解决方案2】:

切勿将 try/catch 用作您的常规程序路径的一部分。它确实很昂贵,并且应该只捕获您无法阻止的错误。 ContainsKey 是这里的方法。

旁注:不。你不会。如果该值很重要,请使用 ContainsKey 检查它是否存在并检索它(如果存在)。不要尝试/捕捉。

【讨论】:

    【解决方案3】:

    旁注:(此时键的值并不重要,否则我会使用 TryGetValue 而不是 ContainsKey)

    您接受的答案是正确的,但只是补充一下,如果您只关心键而不关心值,也许您正在寻找HashSet 而不是Dictionary

    此外,您的第二个代码 sn-p 是一种从字面上添加零值的方法。只需使用ToCheck.ContainsKey(Position),不要创建仅调用该方法并返回其值但不执行其他任何操作的方法。

    【讨论】:

    • 我确实需要这些值,它用于生成地图。附注是因为我看到很多人在与问题无关时问“你为什么不使用 TryGetValue”。但如果需要,我会记住 HashSet,谢谢 :)
    【解决方案4】:

    我知道这是一个老问题,但只是补充一点经验数据...

    在包含 10,000 个条目的字典上运行 50,000,000 次查找并比较完成的相对时间:

    ..如果每次查找都成功:

    • 直线(未经检查)运行需要 1.2 秒
    • 受保护的 (ContainsKey) 运行需要 2 秒
    • 处理(try-catch)运行需要 1.21 秒

    ..如果每 10,000 次查找中有 1 次失败:

    • 受保护的 (ContainsKey) 运行需要 2 秒
    • 处理(try-catch)运行需要 1.37 秒

    ..如果每 10,000 次查找中有 16 次失败:

    • 受保护的 (ContainsKey) 运行需要 2 秒
    • 处理(try-catch)运行需要 3.27 秒

    ..如果每 10,000 次查找中有 250 次失败:

    • 受保护的 (ContainsKey) 运行需要 2 秒
    • 处理(try-catch)运行需要 32 秒

    ..因此,受保护的测试将增加一个恒定的开销,仅此而已,如果 try-catch 测试从不失败,它的运行速度几乎与没有测试一样快,但会根据失败的数量成比例地降低性能。

    我用来运行测试的代码:

    using System;
    using System.Collections.Generic;
    
    namespace ConsoleApplication1
    {
       class Program
       {
          static void Main(string[] args)
          {  Test(0);
             Test(1);
             Test(16);
             Test(250);
          }
    
          private static void Test(int failsPerSet)
          {  Dictionary<int, bool> items = new Dictionary<int,bool>();
    
             for(int i =  0; i < 10000; i++)
                if(i >= failsPerSet)
                   items[i] = true;
    
             if(failsPerSet == 0)
                RawLookup(items, failsPerSet);
    
             GuardedLookup(items, failsPerSet);
    
             CaughtLookup(items, failsPerSet);
    
          }
    
          private static void RawLookup
          (  Dictionary<int, bool> items
          ,  int             failsPerSet
          ){ int                   found = 0;
             DateTime              start ;
    
             Console.Write("Raw     (");
             Console.Write(failsPerSet);
             Console.Write("): ");
    
             start = DateTime.Now;
             for(int i = 0; i < 50000000; i++)
             {  int pick = i % 10000;
                if(items[pick])
                   found++;
             }
    
             Console.WriteLine(DateTime.Now - start);
          }
    
          private static void GuardedLookup
          (  Dictionary<int, bool> items
          ,  int             failsPerSet
          ){ int                   found = 0;
             DateTime              start ;
    
             Console.Write("Guarded (");
             Console.Write(failsPerSet);
             Console.Write("): ");
    
             start = DateTime.Now;
             for(int i = 0; i < 50000000; i++)
             {  int pick = i % 10000;
                if(items.ContainsKey(pick))
                   if(items[pick])
                      found++;
             }
    
             Console.WriteLine(DateTime.Now - start);
          }
    
          private static void CaughtLookup
          (  Dictionary<int, bool> items
          ,  int             failsPerSet
          ){ int                   found = 0;
             DateTime              start ;
    
             Console.Write("Caught  (");
             Console.Write(failsPerSet);
             Console.Write("): ");
    
             start = DateTime.Now;
             for(int i = 0; i < 50000000; i++)
             {  int pick = i % 10000;
                try
                {  if(items[pick])
                      found++;
                }
                catch
                {  
                }
             }
    
             Console.WriteLine(DateTime.Now - start);
          }
    
       }
    }
    

    【讨论】:

    • 谢谢,知道了也太好了!
    • 在实际情况下,ContainsKey 总是更好。
    • +1 正如我所想。如果您想要原始性能并且确信查找很少会失败,那么使用try-catch 而不是ContainsKey 要好得多。
    猜你喜欢
    • 2014-12-18
    • 2019-11-15
    • 2016-02-19
    • 2019-04-06
    • 1970-01-01
    • 2017-07-27
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多