В Java можно использовать AspectJ для добавления поведения до и после выполнения метода, используя аннотации методов. Поскольку атрибуты C # кажутся очень похожими, мне было интересно, возможно ли добиться подобного поведения. Я искал несколько учебных пособий и других источников (1, 2, 3), но ни один из них мне не помог.

Я подумал, что, возможно, мне удастся имитировать поведение, вставив код в конструктор Attribute и сделав его одноразовым, например так:

[AttributeUsage(AttributeTargets.Method)]
public class MyWritingAttribute : Attribute, IDisposable
{
    public MyWritingAttribute()
    {
        Console.WriteLine("Attribute created");
    }

    public void Dispose()
    {
        Console.WriteLine("Attribute disposed");
    }
}

Однако при использовании такого атрибута в консоли отображается только Hello world! :

class Program
{
    static void Main(string[] args)
    {
        SayHelloWorld();
        Console.ReadLine();
    }

    [MyWriting]
    private static void SayHelloWorld()
    {
        Console.WriteLine("Hello World!");
    }
}

Я думал, что, возможно, консоль недоступна в атрибуте, но даже при замене его выражениями throw new Exception() исключение не выдается. Как это возможно, что StringLengthAttribute из EF работает, но мой атрибут даже не создан? И как заставить атрибут работать до и после оформленного метода?

6
lss 1 Сен 2017 в 15:56

4 ответа

Лучший ответ

Как и в случае с Java и AspectJ, для внедрения кода, подобного этому, в .NET требуется отдельный инструмент AoP.

PostSharp - один из таких инструментов, вероятно, самый известный. Я верю, что они поддерживают ядро .NET начиная с версии 5.

5
Anders Forsgren 1 Сен 2017 в 13:02

Это можно сделать с помощью DynamicProxy. Инструкции о том, как это сделать, можно найти здесь AOP в .Net Core с использованием AutoFac и DynamicProxy.

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

var attribute = Attribute.GetCustomAttribute(invocation.MethodInvocationTarget, typeof(CachedAttribute)) as CachedAttribute;
if (attribute != null)
{
  ...
}

Приведенный выше код может быть внутри метода Intercept в реализации Interceptor. CachedAttribute будет вашим атрибутом.

3
Carlos Blanco 12 Фев 2018 в 20:08

Вопрос похож на Запустить метод перед всеми методы класса, поэтому один и тот же ответ применим к обоим. Используйте https://github.com/Fody/Fody. Модель лицензирования основана на добровольных взносах, что делает его лучшим вариантом для PostSharp, что на мой вкус немного дороже.

[module: Interceptor]
namespace GenericLogging
{

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Assembly | AttributeTargets.Module)]
    public class InterceptorAttribute : Attribute, IMethodDecorator
    {
        // instance, method and args can be captured here and stored in attribute instance fields
        // for future usage in OnEntry/OnExit/OnException
        public void Init(object instance, MethodBase method, object[] args)
        {
            Console.WriteLine(string.Format("Init: {0} [{1}]", method.DeclaringType.FullName + "." + method.Name, args.Length));
        }

        public void OnEntry()
        {
            Console.WriteLine("OnEntry");
        }

        public void OnExit()
        {
            Console.WriteLine("OnExit");
        }

        public void OnException(Exception exception)
        {
            Console.WriteLine(string.Format("OnException: {0}: {1}", exception.GetType(), exception.Message));
        }
    }

    public class Sample
    {
        [Interceptor]
        public void Method(int test)
        {
            Console.WriteLine("Your Code");
        }
    }
}

[TestMethod]
public void TestMethod2()
{
    Sample t = new Sample();
    t.Method(1);
}
2
Nigel Findlater 9 Фев 2020 в 05:54

Вам нужен какой-то фреймворк, способный правильно обработать ваш атрибут. Только то, что атрибут существует, не означает, что он будет иметь какой-либо эффект.

Я написал простой движок, который это делает. Он определит, присутствует ли атрибут в переданном action и получит ли отраженные методы для их выполнения.

class Engine
{
    public void Execute(Action action)
    {
        var attr = action.Method.GetCustomAttributes(typeof(MyAttribute), true).First() as MyAttribute;
        var method1 = action.Target.GetType().GetMethod(attr.PreAction);
        var method2 = action.Target.GetType().GetMethod(attr.PostAction);

        // now first invoke the pre-action method
        method1.Invoke(null, null);
        // the actual action
        action();
        // the post-action
        method2.Invoke(null, null);
    }
}
public class MyAttribute : Attribute
{
    public string PreAction;
    public string PostAction;
}

Конечно, вам нужны нулевые щелчки, например в случае, если методы не существуют или не являются статичными.

Теперь вы должны украсить свое действие атрибутом:

class MyClass
{
    [MyAttribute(PreAction = "Handler1", PostAction = "Handler2")]
    public void DoSomething()
    {
        
    }

    public static void Handler1()
    {
        Console.WriteLine("Pre");
    }
    public static void Handler2()
    {
        Console.WriteLine("Post");
    }
}

Наконец, вы можете выполнить этот метод в нашем движке:

var engine = new Engine();
var m = new MyClass();
engine.Execute(m.DoSomething);
8
maytham-ɯɐɥʇʎɐɯ 4 Июл 2020 в 09:41