在本文中,将探讨C#中的两个特性:调用信息属性(Caller Information Attribute)和堆栈跟踪类(StackTrace),它们有助于在运行时获取调用来源的信息。尽管它们的目的相同,但它们之间存在显著的差异。这些信息可以被开发者用于提供跟踪信息。
在开始讨论StackTrace和调用信息之前,先来看一下代码的结构。项目分为三个层次:UI层、业务逻辑层和数据访问层。
public class Program
{
public static void Main(string[] args)
{
try
{
var str = new BusinessEmployee().GetEmployeeList();
Console.WriteLine(str);
Console.ReadLine();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
如上代码所示,UI层调用"GetEmployeeList"方法以获取所有员工的列表。
public class BusinessEmployee
{
private readonly DataEmployee dataEmployee;
public BusinessEmployee()
{
dataEmployee = new DataEmployee();
}
public void GetEmployeeList()
{
dataEmployee.GetEmployeeList();
}
}
在这一层中,业务类调用数据访问层从数据库中获取员工列表。
public class DataEmployee
{
public void GetEmployeeList()
{
// 代码在这里获取数据库
}
}
这一层返回员工列表,但在这个例子中,它返回了一个异常。
C#中的StackTrace类位于System.Diagnostics命名空间中,它在运行时提供与调试器中的调用堆栈窗口相同的信息。为了更好地理解它,让考虑在数据访问层中进行的以下代码更改。
public string GetEmployeeList()
{
StackTrace stackTrace = new StackTrace(true);
StringBuilder sb = new StringBuilder();
foreach (StackFrame frame in stackTrace.GetFrames())
{
sb.AppendLine("Method Name: " + frame.GetMethod().Name + " File Name:" + frame.GetMethod().Module.Name + " Line No: " + frame.GetFileLineNumber());
}
return sb.ToString();
}
如代码所示,它创建了StackTrace类的一个新对象,并且在创建对象时传递了"true"作为StackTrace类构造函数的参数,以捕获文件名、行号和列号。对象创建后,使用GetFrames方法获取每个帧(每个方法调用由一个帧表示)的信息,最后使用StringBuilder将每个帧的详细信息追加起来,然后由前端显示。
运行上述代码后,输出将打印出从一个层到另一个层的每个调用。如果查看上述代码的调用,可以看到调用是从UI层到数据库层。
StackTrace提供了详细的调用来源信息,这是调用信息属性所无法提供的。这是开发者在需要获取跟踪详细信息时可以使用StackTrace的一个原因。StackTrace的帧还提供了对调用方法的控制,并提供了调用来源的程序集。
当代码以Release模式编译时,内联方法不会被列出,而在Debug模式下会被列出。为了理解这一点,考虑以下UI层代码的更改。
如果以Debug模式编译代码,配置如下:
它将为生成以下输出,可以看到四行,即调用方法"GetEmployeeList1"的一行额外调用。
因此,调试器列出了所有方法调用,但现在如果以Release模式编译代码,配置如下:
它将为生成以下输出,可以看到三行。调用方法"GetEmployeeList1"的行缺失了。
因此,StackTrace不会列出编译器转换为内联方法的方法。如果想列出,可以将方法标记为[MethodImpl(MethodImplOptions.NoInlining)]。如果开发者将方法标记为[MethodImpl(MethodImplOptions.AggressiveInlining)],则该方法也不会在调试中列出。
由于StackTrace提供了非常详细的信息,StackTrace将显示完整的跟踪到核心MS源程序集,并会透露正在使用的技术的详细信息,以及可能的版本。这为入侵者提供了可能被利用的潜在弱点的有价值信息。
因此,在显示此信息之前一定要小心,并对此进行安全保护。
它提供了详细的信息,这是调用信息属性无法提供的。 StackFrame提供的行号、文件名和方法名信息无法被开发者更改。但在调用信息属性的情况下,开发者可以通过在调用信息参数中传递错误的值来欺骗。
这是C# 5.0中引入的新特性。属性是System.Runtime.CompilerServices命名空间的一部分。这些属性需要作为可选参数添加到需要获取调用者信息的方法中。
public string GetEmployeeList([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
return "Method Name: " + memberName + " File Name:" + fileName + " Line No: " + lineNumber;
}
运行上述代码将提供以下输出:
调用者属性的信息无法被欺骗,直到开发者在方法参数中传递信息。 这对性能没有运行时成本,即它不会影响代码的性能,因为属性是在编译时添加的。 它对于找出来自未知位置的调用非常有用,例如在"PropertyChange"的情况下。
调用者的信息可以通过开发者在调用者信息参数中传递错误的信息来欺骗。 只有一个StackFrame,即它将提供关于谁调用了方法的信息(方法的直接调用者),但不提供StackTrace那样的详细信息。 它是C# 5.0的一部分,所以它不适用于旧版本的框架。