前篇文章里我谈了谈String.Concat与StringBuilder的实现,于是后来有个朋友回复到:“我发现StringBuilder里好像也用了String.Concat。在一次异常中,Exception的StackTrace显示是先发生了String.Concat异常,然后再StringBuilder.Append。”不过经过一些简单的分析,我们会发现StringBuilder.Append方法的各个重载都没有依赖(即调用)String.Concat方法,那么这样的调用堆栈又是如何形成的呢?
前篇文章里我谈了谈String.Concat与StringBuilder的实现,于是后来有个朋友回复到:“我发现StringBuilder里好像也用了String.Concat。在一次异常中,Exception的StackTrace显示是先发生了String.Concat异常,然后再StringBuilder.Append。”不过经过一些简单的分析,我们会发现StringBuilder.Append方法的各个重载都没有依赖(即调用)String.Concat方法,那么这样的调用堆栈又是如何形成的呢?
展开答案
其实这并不困难,只要简单“推理”一番便可。虽然StringBuilder的Append方法没有依赖String.Concat方法,但这只能说明Append方法没有直接调用String.Concat。然而,只要Append方法有机会调用到我们自定义的逻辑中,便可以由我们的逻辑调用Concat方法。这样的方法可以是Append(object)重载:
public StringBuilder Append(object value)
{
if (value == null)
{
return this;
}
return this.Append(value.ToString());
}
那么,我们的方法可以调用String.Concat方法,但又如何让它抛出异常呢?很容易,因为String.Concat方法也有个Concat(object, object)重载会调用参数的ToString方法。不过,如果我们直接调用String.Concat方法也太不自然了些,相信之前那位朋友也不会这么做。但是,其实我们完全可以:
public class InBuilder
{
public override string ToString()
{
return "Hello" + new InConcat();
}
}
public class InConcat
{
public override string ToString()
{
throw new Exception();
}
}
static void Main()
{
var builder = new StringBuilder();
builder.Append(new InBuilder());
}
看InBuilder的ToString中,将一个字符串与另一个非字符串对象连接起来,而这个连接操作便会被编译为String.Concat(object, object)方法:
.method public hidebysig virtual instance string ToString() cil managed
{
.maxstack 8
L_0000: ldstr "Hello"
L_0005: newobj instance void SimpleConsole.Program/InConcat::.ctor()
L_000a: call string [mscorlib]System.String::Concat(object, object)
L_000f: ret
}
于是执行Main方法便会抛出一个异常,它的StackTrace便是:
at SimpleConsole.Program.InConcat.ToString() in ...
at System.String.Concat(Object arg0, Object arg1)
at SimpleConsole.Program.InBuilder.ToString() in ...
at System.Text.StringBuilder.Append(Object value)
at SimpleConsole.Program.Main() in ...
...
嘿嘿,我是不是很无聊?