c# attribute over main
Asked Answered
I

3

9

Someone asked me a question as to how we can print

line no 1
line no 2
line no 3

Without changing a main method which reads

static void Main(string[] args)
{
    Console.WriteLine("line no 2");
}

Now one approach was to have multiple entry points for the console application. However I tried another approach which goes as follows :

class Program
{
    [Some]
    static void Main(string[] args)
    {
        Console.WriteLine("line no 2");
    }
}
class SomeAttribute : Attribute
{
    public SomeAttribute()
    {
        Console.WriteLine("line no 1");
    }
    ~SomeAttribute()
    {
        Console.WriteLine("line no 3");
    }
}

When I apply a breakpoint on each of the WriteLine, I am able to see that the approach works, however, the same isn't reflected on the console.

Just curious.

Idealist answered 13/4, 2013 at 10:53 Comment(4)
Without changing a main method which reads ... really?!?Simferopol
@MichaelPerrenoud: I'm pretty sure this is an exercise that is supposed to take some clever thinking. I honestly think it's pretty interesting, in fact.Byzantium
@ArnoSluismans, that's fine. And it's really simple, it was just really weird. But thanks for heading me down the right path, have a look at my answer.Simferopol
The solutions below are all helpful, but I am curious as to why despite hitting the breakpoints, as expected, the attribute technique doesn't work.Idealist
J
15

You're problem can be broken down into the search of the hooks, which are triggered before and after Main method execution of the console application.

  • First hook is a Program static constructor, which is guarantee to execute before Main method in Program class.

  • Second is an event ProcessExit of a AppDomain, which "Occurs when the default application domain's parent process exits". You can use static constructor to subscribe to this event.


class Program
{
    static Program()
    {
        Console.WriteLine("line no 1");

        AppDomain.CurrentDomain.ProcessExit += 
                                          (s, a) => Console.WriteLine("line no 3");
    }

    static void Main(string[] args)
    {
        Console.WriteLine("line no 2");
    }
}

prints:

line no 1
line no 2
line no 3

Next part would be quite a long one. I'll try to explain what is the problem with SomeAttribute in your question.

First of all, consider this StackOverflow question to know exactly when custom attributes constructors are executed. This isn't so simple, as it might seem at first glance.

As we already know, ctor of custom attribute will only be executed, when you will access it via reflection. So in you example simple program execution won't trigger attribute constructor. But why do your breakpoint hit, when you apply SomeAttribute to Main method? It turns out, that visual studio uses reflexion to find out main method and attach a debugger to your application. But there is no console window at that point. So statement Console.WriteLine is useless and produce to effect. Moreover, it seems to block all next statements to console output.

So next code will produce different results, depending if you run it with VS debugger or not:

class Program
{
    [MyAttribute]
    static void Main()
    {

    }
}

class MyAttribute : Attribute
{
    public MyAttribute()
    {
        MessageBox.Show("MyAttribute ctor");
    } 
}

If you run it without debugger (Ctrl + F5 in VS default configuration), you'll see, that program terminates and no windows appear. When you execute it with debugger (F5) you'll see

enter image description here

and no console window next to VS, only win forms icon: enter image description here

As I've described earlier, when you try to write to console when there is no one, then all other calls to Console.WriteLine doesn't affect your console application. That's why you can see any console messages, even if you step a breakpoint in the constructor.

Jotham answered 13/4, 2013 at 11:8 Comment(2)
+1 for the answer, but I was trying to figure out why my solution didn't work.Idealist
@Idealist I've updated my answer with explanations to your observed behaviourJotham
K
6

I think Ilya Ivanov's answer is possibly the best one. However consider mine as a funny answer too:

public class Program
{
    static Program()
    {
        Console.WriteLine("line no 1");
        Console.WriteLine("line no 2");
        Console.WriteLine("line no 3");
        Environment.Exit(0);
    }

    static void Main(string[] args)
    {
        Console.WriteLine("line no 2");
    }
}
Kazan answered 13/4, 2013 at 11:17 Comment(1)
+1 for thinking out of the box. There is no need to execute Main method, nice corner case inside problem statement.Jotham
S
4

Let's use AOP, and let's leverage PostSharp. You're going to need to download and install it first, and then after that you'll need to add a reference to it using NuGet. You have to install it because it's a hook into the compiler. See, when you compile your code, PostSharp actually injects IL into the output based on the hooks you use.

Once you've done those two things add a new class for the AOP attribute:

using PostSharp.Aspects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

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");
        }
    }
}

and then modify your Main method like this:

[ConsoleAspect]
static void Main(string[] args)
{
    Console.WriteLine("line no 2");
}
Simferopol answered 13/4, 2013 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.