c# 属性覆盖主函数
c# 属性覆盖主函数
有人问我一个问题,即如何在不改变读取的主方法的情况下打印以下内容:
第一行 第二行 第三行
现在有一种方法是为控制台应用程序设置多个入口点。然而,我尝试了另一种方法,如下所示:
class Program { [Some] static void Main(string[] args) { Console.WriteLine("第二行"); } } class SomeAttribute : Attribute { public SomeAttribute() { Console.WriteLine("第一行"); } ~SomeAttribute() { Console.WriteLine("第三行"); } }
当我在每个WriteLine上设置断点时,我发现这种方法是有效的,但是在控制台上却没有显示出来。
只是好奇。
问题出现的原因是使用了PostSharp库来实现AOP功能,但在主函数上应用特性时出现了错误。解决方法是引入PostSharp库,并创建一个AOP特性类,在特性类中重写OnEntry和OnExit方法,在主函数上应用该特性类。
首先,需要下载并安装PostSharp库,然后使用NuGet添加对PostSharp库的引用。这是因为PostSharp库是编译器的一个钩子,当编译代码时,它会根据使用的钩子将IL代码注入到输出中。
接下来,创建一个AOP特性类,代码如下:
using PostSharp.Aspects; using System; namespace ConsoleApplication2 { [Serializable] public class ConsoleAspect : OnMethodBoundaryAspect { public override void OnEntry(MethodExecutionArgs args) { base.OnEntry(args); Console.WriteLine("line no 1"); } public override void OnExit(MethodExecutionArgs args) { base.OnExit(args); Console.WriteLine("line no 3"); } } }
然后,在主函数上应用该特性类,代码如下:
[ConsoleAspect] static void Main(string[] args) { Console.WriteLine("line no 2"); }
通过以上操作,我们就可以在主函数中实现AOP功能,并在控制台输出相应的日志信息。
在上述代码中,我们看到一个名为`Program`的类,它包含了一个静态构造函数和一个`Main`方法。静态构造函数在类的第一次被使用之前被调用,并且只会执行一次。在这个例子中,静态构造函数中的代码会在程序运行之前被执行。
静态构造函数中的代码输出了三行文本,并且在最后一行代码中调用了`Environment.Exit(0)`。这个方法会立即终止程序的执行。
然而,在`Main`方法中,我们只输出了一行文本。这就是问题所在:由于静态构造函数在`Main`方法之前被执行,并且在其中调用了`Environment.Exit(0)`,所以`Main`方法中的代码根本没有机会被执行。
解决这个问题的方法是将`Environment.Exit(0)`的调用从静态构造函数中移除,或者将其放在一个条件语句中,以便只在特定条件下执行。
以下是修复后的代码示例:
public class Program { static Program() { Console.WriteLine("line no 1"); Console.WriteLine("line no 2"); Console.WriteLine("line no 3"); } static void Main(string[] args) { if (!Environment.GetCommandLineArgs().Contains("/exit")) { Console.WriteLine("line no 2"); } } }
在修复后的代码中,我们将`Environment.Exit(0)`的调用移除,并在`Main`方法中添加了一个条件语句。这个条件语句检查命令行参数,如果参数中不包含`/exit`,则输出"line no 2"。这样,我们就能够在不终止程序执行的情况下输出所需的文本。
通过这种修复,我们可以确保在`Main`方法中的代码得到执行,而不会被静态构造函数中的代码所阻止。
问题出现的原因是在执行控制台应用程序的Main
方法之前和之后触发的钩子的搜索问题。
- 第一个钩子是
Program
静态构造函数,在Program
类中的Main
方法之前执行。 - 第二个是
AppDomain
的事件ProcessExit
,它在“默认应用程序域的父进程退出时发生”。您可以使用静态构造函数订阅此事件。
接下来的部分将会很长。我将尝试解释您问题中的SomeAttribute
的问题所在。
首先,要了解确切的情况,可以参考这个StackOverflow问题,了解custom attributes
构造函数何时执行。这并不像乍一看那么简单。
正如我们已经知道的那样,只有当您通过反射访问时,自定义属性的构造函数才会被执行。所以在您的示例中,简单的程序执行不会触发属性构造函数。但是为什么当您将SomeAttribute
应用于Main
方法时,断点却命中了呢?原来,Visual Studio使用反射来找到主方法并将调试器附加到您的应用程序上。但此时还没有控制台窗口。所以Console.WriteLine
语句是无用的,也不会产生效果。此外,它似乎会阻塞所有后续的控制台输出语句。
因此,下面的代码将根据您是否使用VS调试器来产生不同的结果:
class Program { [MyAttribute] static void Main() { } } class MyAttribute : Attribute { public MyAttribute() { MessageBox.Show("MyAttribute ctor"); } }
如果您在没有调试器的情况下运行它(在VS默认配置中,按Ctrl + F5),您将看到程序终止且没有窗口出现。当您使用调试器执行它时(F5),您将看到:
并且在VS旁边没有控制台窗口,只有窗体图标:
如前所述,当您尝试在没有控制台窗口的情况下写入控制台时,所有其他对Console.WriteLine
的调用都不会影响您的控制台应用程序。这就是为什么即使在构造函数中设置断点,您也看不到任何控制台消息的原因。
对于这个答案给予+1,但我想弄清楚为什么我的解决方法不起作用。
我已更新我的答案,解释了您观察到的行为。