Порядок выполнения аспектов после обработки

#c# #visual-studio-2012 #aop #postsharp

#c# #visual-studio-2012 #aop #postsharp

Вопрос:

Хорошо, это может занять много времени. Я пытаюсь сделать две вещи:

  • Я хочу иметь класс, который реализует интерфейс, удерживая экземпляр другого класса, к которому направляется каждый вызов.

  • Я также хочу перехватывать все вызовы методов и что-то делать.

Выполнение обоих самостоятельно отлично работает. Кажется, что их объединение работает только в одном порядке выполнения, и, как говорит Мерфи, это неправильный (по крайней мере, для меня).

Я хотел бы сначала ввести композицию, чтобы перехват всех вызовов также перехватывал те, которые были введены ранее.

 namespace ConsoleApplication13
{
  using System;
  using System.Reflection;

  using PostSharp;
  using PostSharp.Aspects;
  using PostSharp.Aspects.Dependencies;
  using PostSharp.Extensibility;

  [Serializable]
  [ProvideAspectRole("COMPOSER")]
  public sealed class ComposeAspectAttribute : CompositionAspect
  {
    [NonSerialized]
    private readonly Type interfaceType;

    private readonly Type implementationType;

    public ComposeAspectAttribute(Type interfaceType, Type implementationType)
    {
      this.interfaceType = interfaceType;
      this.implementationType = implementationType;
    }

    // Invoked at build time. We return the interface we want to implement. 
    protected override Type[] GetPublicInterfaces(Type targetType)
    {
      return new[] { this.interfaceType };
    }

    // Invoked at run time. 
    public override object CreateImplementationObject(AdviceArgs args)
    {
      return Activator.CreateInstance(this.implementationType);
    }
  }

  [Serializable]
  [ProvideAspectRole("INTERCEPTOR")]
  [MulticastAttributeUsage(MulticastTargets.Method)]
  [AspectRoleDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, "COMPOSER")]
  public sealed class InterceptAspectAttribute : MethodInterceptionAspect
  {
    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
      base.CompileTimeInitialize(method, aspectInfo);

      // Warning in VS output
      Message.Write(method, SeverityType.Warning, "XXX", "Method: "   method.Name);
    }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
      Console.WriteLine("Intercepted before");
      args.Proceed();
      Console.WriteLine("Intercepted after");
    }
  }

  interface ITest
  {
    void Call();
  }

  class TestImpl : ITest
  {
    public void Call()
    {
      Console.WriteLine("CALL remote implemented");
    }
  }

  [InterceptAspect(AspectPriority = 1)]
  [ComposeAspect(typeof(ITest), typeof(TestImpl), AspectPriority = 2)]
  class Test
  {
    // this should, after compilation, have all methods of ITest, implemented through an instance of TestImpl, which get intercepted before TestImpl is called

    public void CallLocalImplementedTest()
    {
      Console.WriteLine("CALL local implemented");
    }
  }


  class Program
  {
    static void Main()
    {
      var test = new Test();

      ITest t = Post.Cast<Test, ITest>(test);

      Console.WriteLine("TEST #1");
      t.Call();

      Console.WriteLine("TEST #2");
      test.CallLocalImplementedTest();

      Console.ReadLine();
    }
  }
}
  

Я попытался повлиять на порядок выполнения двух аспектов с помощью

  • AspectRoleDependency, заставляющий перехватчик зависеть от композитора для запуска первым

  • AspectPriority, также заставляющий композитора запускаться первым.

Поскольку тесты всегда дают

 TEST #1
CALL remote implemented

TEST #2
Intercepted before
CALL local implemented
Intercepted after
  

очевидно, что это не работает. У вас есть подсказка, почему мой порядок выполнения не изменился? Я сделал что-то не так, я пропустил деталь в документации? Что я могу сделать, чтобы также перехватить мои методы, введенные с помощью композиции?

Комментарии:

1. Вам также необходимо добавить InterceptAspect в TestImpl класс для достижения желаемого результата.

2. @nemesv Хотя это, вероятно, сработало бы, класс TestImpl иногда не является моим классом. Я бы предпочел решение, в котором я мог бы оставить TestImpl как есть.

3. Альтернативно, вы можете поместить свой [InterceptAspect(AttributeInheritance = MulticastInheritance.Multicast)] в сам ITest интерфейс. Но это самое большее, что вы можете получить. Проблема в том, что Postsharp выполняет изменение IL за один шаг и применяет только InterceptAspect к методам, которые присутствуют во время компиляции, поэтому он не видит новых реализаций интерфейса, добавленных с ComposeAspect помощью . Таким образом, тип, который вы добавляете с ComposeAspect помощью, уже должен содержать код протоколирования, предоставленный InterceptAspect с помощью, помещая его в ITest или в TestImpl класс.

4. Итак, вам удалось решить эту проблему?

5. @nemesv Спасибо за вашу помощь. Хотя вы были правы в том, как заставить его работать независимо от порядка, добавление чего-либо в интерфейс или реализацию в фоновом режиме приведет к нарушению цели аспекта, чтобы иметь его только в одном месте. Итак, я решил наш бизнес-кейс с другим набором аспектов и самостоятельно реализовал композицию. Если вы хотите опубликовать обходные пути в качестве ответа, я буду рад принять его, поскольку кажется, что «это работает не так, как вы хотите», настолько близко к ответу, насколько я получу.

Ответ №1:

С текущими аспектами и вашей текущей настройкой вы не сможете достичь желаемого результата.

Проблема заключается в том, как работает Postsharp: он выполняет изменение IL за один шаг и применяет только InterceptAspect к методам, которые присутствуют в исходное время компиляции, поэтому он не видит новых реализаций интерфейса, добавленных с ComposeAspect помощью .

Таким образом, никакой порядок принятия или предоставления ролей, приоритетов или другой конфигурации здесь не поможет.

Одним из обходных путей было бы добавление InterceptAspect в введенный TestImpl класс:

 [InterceptAspect]
class TestImpl : ITest
  {
    public void Call()
    {
      Console.WriteLine("CALL remote implemented");
    }
  }
  

В этом случае логика ведения журнала будет добавлена напрямую TestImpl , поэтому этот метод будет содержать ведение журнала, когда он будет включен в ваш Test класс.

Или, если вы не отмечаете каждую реализацию, вы можете поместить свой аспект в сам интерфейс с помощью:

 [InterceptAspect(AttributeInheritance = MulticastInheritance.Multicast)]
interface ITest
{
   void Call();
}