#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();
}