C#异常消息与性能优化:未抛出异常时是否仍会创建消息字符串?
我知道这看起来像是微优化,但我要运行数千万次循环,性能是核心关键问题。每次循环里都会执行如下检查:
if (this.isSafeMode && bar.Period != this.SourcePeriod) { throw new InvalidOperationException( "Attempted to process a bar that does not match the market context's source period. " + $"Processed bar period: [{bar.Period}] " + $"Market context source period: [{this.SourcePeriod}]"); }
问题1
即使没有抛出异常,每次循环都会创建这个异常消息字符串,从而产生不必要的性能开销吗?(其他场景里我需要构建开销更大的异常消息,这里只是简单示例)
问题2
如果问题1的答案是肯定的,创建一个能接收参数、并根据参数构建消息字符串的自定义异常类型,能不能避免这种不必要的开销?或者有没有其他替代方案?
补充说明:该异常不需要适配多语言翻译(如果有这个需求的话,自定义异常会更合理)
回答
问题1的答案
是,即使不抛出异常,你当前的写法也会产生不必要的性能开销。因为C#的字符串插值($"")和字符串拼接操作,会在throw语句执行前就完成——只要进入if条件判断的代码路径,不管最终会不会抛出异常,这些字符串构建工作都会先执行。在数千万次循环的场景下,这种累积的开销会非常明显。
问题2的解决方案
方案1:延迟构建异常消息(自定义异常)
自定义异常确实能解决这个问题。你可以把需要的参数(比如bar.Period和SourcePeriod)传入自定义异常的构造函数,然后重写Message属性,只在需要获取异常消息的时候才拼接字符串。这样只有当异常真的被抛出、且消息被访问时,才会执行字符串构建操作,完全避免无异常时的额外开销。
示例代码:
public class MismatchedBarPeriodException : InvalidOperationException { private readonly string _processedPeriod; private readonly string _sourcePeriod; public MismatchedBarPeriodException(string processedPeriod, string sourcePeriod) { _processedPeriod = processedPeriod; _sourcePeriod = sourcePeriod; } public override string Message { get { return $"Attempted to process a bar that does not match the market context's source period. Processed bar period: [{_processedPeriod}] Market context source period: [{_sourcePeriod}]"; } } }
循环内的调用代码改为:
if (this.isSafeMode && bar.Period != this.SourcePeriod) { throw new MismatchedBarPeriodException(bar.Period.ToString(), this.SourcePeriod.ToString()); }
方案2:轻量替代——延迟字符串构建
如果不想自定义异常,也可以把字符串拼接逻辑封装成单独方法,仅在确定要抛出异常时才调用:
private string GetMismatchedPeriodMessage(string processedPeriod, string sourcePeriod) { return $"Attempted to process a bar that does not match the market context's source period. Processed bar period: [{processedPeriod}] Market context source period: [{sourcePeriod}]"; } // 循环内代码 if (this.isSafeMode && bar.Period != this.SourcePeriod) { throw new InvalidOperationException(GetMismatchedPeriodMessage(bar.Period.ToString(), this.SourcePeriod.ToString())); }
这种方式同样能保证只有if条件成立时,才会执行字符串构建,无异常时不会产生额外开销。
方案3:极致优化(参数类型固定时适用)
如果Period是枚举或有限固定值,可以提前缓存消息模板,用string.Format替换参数,减少重复的字符串拼接开销。不过这种场景下收益有限,前面两种方案已经足够解决核心问题。
内容的提问来源于stack exchange,提问作者Sean

