Imagine that all the bussines logic codes of the project you wrote are finished and you do not have time to breathe, and at the same time, the necessary tests are coming. Consider that although the program is complete, there are performance problems in some methods and you will have to write the necessary log or tools to examine it. In such a case, how do you do this in a nice way without breaking the Bussines Logic codes?

Tracking Issues

As a result of your monitoring, you found a method that causes performance problems due to its slow operation, and you cannot do it in debug because it is online. To track it, you do a tracking in log form:

class Foo1
{
    void Do()
    {
        //Logging start
        //Performance monitoring starts
        DoSomething();
        //End of log record
        //End of performance monitoring
    }
}

Looks fine. After solving the problem here, you noticed another problem, “Handle” and then you’re reviewing it again:

class Foo2
{
    void Handle()
    {
		//Logging start
        //Performance monitoring starts
        DoSomething();
        //End of log record
        //End of performance monitoring
    }
}

Although the two blocks of code above are ugly, you managed to fix the problem. But coding like this is not appropriate:

  • They are not reusable and there is constant code duplication.
  • Logging, Performance monitoring, they are confusing by occupying the Business Logic codes.

After realizing these issues, your second form of code would look like this:

class Bar
{
    void Do()
    {
        common.BeginLog();
			common.BeginWatch();
				common.BeginTransaction();
        			foo.Do();
				common.Commit();
			common.EndWatch();
        common.EndLog();
    }
}

Seems like a good solution but still doesn’t solve the main problem. Even though common works are under “common”, it still breaks Bussines Logic codes in many places. The hierarchical structure of this code is as in the picture below:

Transaction UML

Aspect-Oriented Programming (AOP)

What is AOP?

  • Abbreviation: Abbreviation for Aspect Oriented Programming.
  • Application scenario: Provides good reuse and separation for methods not related to any Bussines Logic. For example security control, exception handling. But it must be operated on a global scale.
  • Core concept: AOP thinking is to operate around the main idea. Aspect is to prevent some operations of the target object, to prevent the operations of some other parts of the system on the target object and to make a real performance measurement by doing some operations before and after the Bussines code.
  • Implementation mode: AOP implementation is divided into two. “static” and “dynamic”. Static, placing the necessary codes in the IL code through the compiler. Dealing with a dynamic proxy object at runtime.
    Note: This topic may seem complicated, it is useful to read AOP from different sources.

What is the Cut Noodles?
Aspect Oriented Programming has 6 characters and the most difficult is to understand Aspect.

  • In real life, a large watermelon is cut in half with a knife and its integrity is broken. The flesh of the watermelon is completely opened to you and you can eat it however you want, but you cannot eat it without cutting it.
  • In the computer world class(object) grasps several methods and with this direction it looks like a whole watermelon. You split it by choosing an entry point. The method is completely open to you, you can do any blocking according to your needs. But if you can’t break the method, you can’t do these interceptions. There is no blade available in the computer world of course, but there is another abstract concept, as explained below:

Vertical inheritance for methods interference:

class Order
{
    public virtual void Add()
    {
        Console.WriteLine("New Order");
    }
}

class OrderExt : Order
{
    public override void Add()
    {
        //Open transaction
        BeginTransaction();
        base.Add();
        //Commit transaction
        Commit();
    }
    void BeginTransaction() { }
    void Commit() { }
}

The problems here are the ones we talked about earlier. That’s why we don’t repeat.
Using AOP horizontal method manipulation with a dynamic Proxy class.

class Order
{
    public virtual void Add()
    {
        Console.WriteLine("New Order");
    }
}

class TransactionUtility
{
    public void BeginTransaction() { }
    public void Commit() { }
}

class OrderProxy
{
    public Order o;
    public TransactionUtility u;
    public void Add()
    {
        u.BeginTransaction();
        o.Add();
        u.Commit();
    }
}

Of course, the OderProxy object here is dynamically created at runtime with the help of a framework. The client does not know that the called object is actually an OrderProxy. An entire process is shown below:

Aspect UML

  1. Target: The target class to be proxyed.
  2. Join Point: The point to be connected, the methods that may require intervention.
  3. Point Cut: The entry point obtained by the Connection Point.
  4. Aspect Class: Provides common tasks for process management, logging, performance monitoring and other operations.
  5. Advice: Notification point, improved code.
  6. Weaving: Applies enhancements to the target object, where a new Proxy object is created.
  7. Proxy: Proxy class.

Using AOP in Unity3D

There are many good AOP Frameworks for .NET but none for Unity yet. Be aware that many technologies will not be supported as it is Cross-Platform. So we will implement it in a different form. From what we understand, we should focus on two points:

  • Since there is no framework to create a dynamic proxy object, we will create it ourselves.
  • The proxy object will be used to manipulate the method. In this case, we will send the proxy object how it will interfere with the method by making a delegate like a strategy.

Proxy identification, a class to be proxyed, a certain method and intervention strategy:

public class Proxy
{
    public static Proxy Instance = new Proxy();

    private IInvocationHandler _invocationHandler;
    private object _target;
    private string _method;
    private object[] _args;

    private Proxy()
    {
    }

    public Proxy SetInvocationHandler(IInvocationHandler invocationHandler)
    {
        _invocationHandler = invocationHandler;
        return this;
    }

    public Proxy SetTarget(object target)
    {
        _target = target;
        return this;
    }

    public Proxy SetMethod(string method)
    {
        _method = method;
        return this;
    }

    public Proxy SetArgs(object[] args)
    {
        _args = args;
        return this;
    }

    public object Invoke()
    {
        var methodInfo = _target.GetType().GetMethod(_method);
        return _invocationHandler.Invoke(_target, methodInfo, _args);
    }
}

Interface of the intervention strategy:

public interface IInvocationHandler
{
    void PreProcess();
    object Invoke(object proxy, MethodInfo method, object[] args);
    void PostProcess();
}

An implemented example:

public class LogInvocationHandler:IInvocationHandler
{
    public void PreProcess()
    {
        LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Pre Process");
    }

    public object Invoke(object target, MethodInfo method, object[] args)
    {
        PreProcess();
        var result= method.Invoke(target, args);
        PostProcess();
        return result;
    }

    public void PostProcess()
    {
        LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Post Process");
    }
}

Now for the use case, let’s imagine we’re going to interfere with the test method in the repository. It will print the log before and after:

 Proxy.Instance.SetTarget(repository)
	.SetMethod("Test")
	.SetArgs(new object[] {})
	.SetInvocationHandler(new LogInvocationHandler())
	.Invoke();