尝试捕获每一行代码,而无需单独的try-catch块
我目前没有这个问题,但是我们永远都不会知道,而且思想实验总是很有趣。
忽略尝试甚至要尝试执行此操作所必须面对的明显问题,让我们假设我们拥有别人设计的可怕代码,而我们需要在同一代码中进行一系列广泛而多样的操作阻止,例如:
WidgetMaker.SetAlignment(57); contactForm["Title"] = txtTitle.Text; Casserole.Season(true, false); ((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true;
乘以一百。其中一些可能会起作用,另一些可能会严重出错。我们需要的是"下一次错误恢复"的等效项,否则我们将最终在许多代码行中复制并粘贴try-catches。
我们将如何解决这个问题?
解决方案
在Cworld中,On Error Resume Next
是一个非常糟糕的主意。添加与" On Error Resume Next"等效的内容也不会真正。它所要做的就是使我们处于不良状态,这可能会导致更多的细微错误,数据丢失甚至可能损坏数据。
但是要给提问者以应有的责任,我们可以添加一个全局处理程序并检查TargetSite以查看哪个方法失败了。然后,我们至少可以知道它中断了哪条线。下一部分将尝试找出与调试器相同的方式设置"下一条语句"。希望此时堆栈不会解开,或者我们可以重新创建它,但是肯定值得一试。但是,使用这种方法,代码每次都必须在Debug模式下运行,这样调试符号就会包括在内。
不幸的是,我们可能不走运。在错误恢复上,下一步是一个遗留选项,通常不鼓励使用,并且不具备我在C#中的知识。
我建议将代码保留在VB中(根据我们对OnError ResumeNext的特定要求,这听起来像是源代码),并与实现所需新代码的Cdll或者exe进行交互。然后执行重构以使代码安全,然后将此安全代码转换为Cas。
重写代码。尝试查找在逻辑上相互依赖的语句集,这样,如果一个语句失败,那么下一个语句就没有意义了,如果我们想忽略它们的结果,则将它们分配到它们自己的函数中并用try-batch包围它们那并继续。
每行一次高亮显示,一次"环绕"尝试/捕获。这样就避免了我们提到的复制粘贴
重构为单独的,命名良好的方法:
AdjustFormWidgets(); SetContactTitle(txtTitle.Text); SeasonCasserole();
其中的每一个都受到适当的保护。
如果可以让编译器为该代码提供一个表达式树,则可以通过用包装原始语句的新try-catch块替换每个语句来修改该表达式树。这并不像听起来那样牵强。对于LINQ,获得了将lambda表达式捕获为表达式树的功能,可以在运行时在用户代码中对其进行操作。
在.NET 3.5中,今天这种方法是不可能的-if除了System.Linq.Expressions中缺少" try"语句外,没有其他原因。但是,在Conce的未来版本中,DLR和LINQ表达式树的合并已完成非常有可能。
忽略所有我们想要避免这样做的原因。
如果仅是需要减少行数,则可以尝试执行以下操作:
int totalMethodCount = xxx; for(int counter = 0; counter < totalMethodCount; counter++) { try { if (counter == 0) WidgetMaker.SetAlignment(57); if (counter == 1) contactForm["Title"] = txtTitle.Text; if (counter == 2) Casserole.Season(true, false); if (counter == 3) ((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true; } catch (Exception ex) { // log here } }
但是,如果我们尝试重用任何调用结果,则必须注意可变范围。
这是拥有预处理器有用的事情之一。我们可以定义一个吞入异常的宏,然后使用快速脚本将该宏添加到所有行。
因此,如果这是C ++,则可以执行以下操作:
#define ATTEMPT(x) try { x; } catch (...) { } // ... ATTEMPT(WidgetMaker.SetAlignment(57)); ATTEMPT(contactForm["Title"] = txtTitle.Text); ATTEMPT(Casserole.Season(true, false)); ATTEMPT(((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true);
不幸的是,似乎没有多少语言像C / C ++那样包含预处理器。
我们可以创建自己的预处理器,并将其添加为预构建步骤。如果我们想完全自动化,则可以编写一个预处理器,该预处理器将接收实际的代码文件,并单独添加try / catch内容(因此我们不必手动将这些ATTEMPT()块添加到代码中) 。确保它仅修改了原本可能很困难的行(必须跳过变量声明,循环构造等,以免破坏构建)。
但是,我认为这些想法很可怕,绝不应该做,但有人提出了这个问题。 :)
确实,我们永远都不应该这样做。我们需要查找导致错误的原因并进行修复。吞咽/忽略错误是一件坏事,因此,我认为这里的正确答案是"修复错误,不要忽略它!"。 :)
我们可以查看如何集成企业库的异常处理组件,以获取有关如何处理未处理的异常的一种想法。
如果这是针对ASP.Net应用程序的,则Global.asax中有一个称为" Application_Error"的函数,在大多数情况下会被调用,而灾难性故障通常是另一种情况。
我们可以使用goto,但是它仍然很混乱。
我实际上一直想要一种单语句try-catch。在某些情况下会很有用,例如添加日志记录代码,或者我们不想在主程序流失败时中断它。
我怀疑可以使用与linq相关的某些功能来完成某些工作,但是目前还没有足够的时间来研究它。如果我们只是找到一种将语句包装为匿名函数的方法,然后使用另一种方法在try-catch块中调用它就可以了……但不确定是否可行。
快速失败
详细地说,我想我是在质疑这个问题。如果引发异常,为什么要让代码继续执行就好像什么都没发生一样?我们可能希望在某些情况下出现异常,在这种情况下,我们应在该代码周围编写一个try-catch块并进行处理,或者出现意外错误,在这种情况下,我们应该希望应用程序中止,重试或者失败。不要像受伤的僵尸mo吟的"大脑"那样继续前进。
这可以确定问题最多的部分。
@ JB金
谢谢你提醒我。日志记录应用程序块具有一个可用于跟踪事件的检测事件,我们可以在MS Enterprise库文档中找到更多信息。
Using (New InstEvent) <series of statements> End Using
此使用过程中的所有步骤都将追溯到一个日志文件,我们可以解析该日志以查看日志中断的地方(抛出异常)并找出违规者。
重构确实是我们最好的选择,但是如果我们有很多事情,这可能会确定最严重的违规者。
我会说什么都不做。
是的,没事。
我们向我明确指出了两件事:
- 我们知道架构令人厌烦。
- 有很多这样的废话。
我说:
- 没做什么。
- 添加一个全局错误处理程序,以便每次发送电子邮件时向我们发送电子邮件。
- 等到某物掉落(或者测试失败)
- 更正该错误(在页面范围内根据需要进行重构)。
- 每次出现问题时重复一次。
如果那样糟的话,我们会立即将其清除。是的,我知道这听起来很烂,我们可能一开始就需要修正一些错误,但是它可以让我们在大量的代码生效之前修复有需要的代码,无论它多么糟糕。看起来。
一旦我们赢得了战争,我们将对代码有更好的了解(由于所有重构),我们将有一个更好的构想。
尝试将所有内容包装在气泡包装纸上可能只需要很长的时间,而我们仍将无法更接近解决问题。
public delegate void VoidDelegate(); public static class Utils { public static void Try(VoidDelegate v) { try { v(); } catch {} } } Utils.Try( () => WidgetMaker.SetAlignment(57) ); Utils.Try( () => contactForm["Title"] = txtTitle.Text ); Utils.Try( () => Casserole.Season(true, false) ); Utils.Try( () => ((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true );
很明显,我们将在VB.NET中编写代码,该代码实际上具有On Error Resume Next,并将其以DLL格式导出到C#。什么都只是being嘴
惩罚
就像有人提到的那样,VB允许这样做。在C#中以相同的方式进行操作怎么样?输入可信赖的反射器:
这:
Sub Main() On Error Resume Next Dim i As Integer = 0 Dim y As Integer = CInt(5 / i) End Sub
转换为:
public static void Main() { // This item is obfuscated and can not be translated. int VB$ResumeTarget; try { int VB$CurrentStatement; Label_0001: ProjectData.ClearProjectError(); int VB$ActiveHandler = -2; Label_0009: VB$CurrentStatement = 2; int i = 0; Label_000E: VB$CurrentStatement = 3; int y = (int) Math.Round((double) (5.0 / ((double) i))); goto Label_008F; Label_0029: VB$ResumeTarget = 0; switch ((VB$ResumeTarget + 1)) { case 1: goto Label_0001; case 2: goto Label_0009; case 3: goto Label_000E; case 4: goto Label_008F; default: goto Label_0084; } Label_0049: VB$ResumeTarget = VB$CurrentStatement; switch (((VB$ActiveHandler > -2) ? VB$ActiveHandler : 1)) { case 0: goto Label_0084; case 1: goto Label_0029; } } catch (object obj1) when (?) { ProjectData.SetProjectError((Exception) obj1); goto Label_0049; } Label_0084: throw ProjectData.CreateProjectError(-2146828237); Label_008F: if (VB$ResumeTarget != 0) { ProjectData.ClearProjectError(); } }
为什么不在C#中使用反射?我们可以创建一个反映代码的类,并使用行号作为在每个单独的try / catch块中放置内容的提示。这有一些优点:
- 它稍微不那么丑陋,因为它实际上并不需要我们修改源代码,并且只能在调试模式下使用它。
- 在实现它的同时,我们会学到一些有关c#的有趣知识。
但是,我建议不要采取任何此类措施,除非我们当然要接管保养工作,并且我们需要处理这些异常,以便可以对其进行修复。也许会很有趣。
有趣的问题;非常可怕
如果可以使用宏,那就太好了。但这是C#爆炸的代码,因此我们可以使用一些预处理器工作或者一些外部工具来解决它,以将行包装在单独的try-catch块中。不知道我们是要手动包装它们还是要完全避免尝试捕获。
为此,我试图给每条线贴上标签,并从一个接发处跳回去,但运气不佳。但是,克里斯托弗(Christopher)发现了正确的方法。 Dot Net Thoughts和Mike Stall的.NET Blog中对此进行了一些有趣的添加讨论。
编辑:当然。列出的try-catch
/switch-goto
解决方案实际上不会编译,因为catch
中的try
标签不在范围内。任何人都知道制作这样的东西缺少什么吗?
我们可以使用编译器的预处理步骤来自动执行此操作,也可以修改Mike Stall的Inline IL工具来注入一些无知的错误。
(Orion Adrian关于检查异常并尝试设置下一条指令的答案也很有趣。)
总而言之,这似乎是一个有趣而有启发性的练习。当然,我们必须决定在哪一点上模拟ON ERROR RESUME NEXT的努力胜于修复代码的努力。 :-)