Базовая реализация атрибута AOP, использующего стандартную платформу .NET Framework - программирование
Подтвердить что ты не робот

Базовая реализация атрибута AOP, использующего стандартную платформу .NET Framework

Возможный дубликат:
Метод переноса С# через атрибуты

Я хотел бы достичь такой функциональности:

[Atomic]
public void Foo()
{           
    /* foo logic */
}

Где атрибут [Atomic] - это атрибут, который переносит функциональную логику в пределах транзакции:

using(var scope = new TransactionScope())
{
    /* foo logic */
    scope.Complete();
}

Как написать такой атрибут?

Я попросил в основном тот же question, я знаю, что это можно сделать с помощью AOP, но я не упоминал, что я ищу некоторые простейшее доказательство реализации концепции или полезные статьи, которые могут помочь мне написать это, используя чистую .NET Framework (я полагаю, используя типы RealProxy и MarshalByRefObject, о которых я прочитал связанные с просмотром вопросы).

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

4b9b3361

Ответ 1

Кажется, что основная вещь...

Это одна из (многих) вещей, которые просты в понимании концепции, но не совсем просты в реализации.

Согласно Oded answer, атрибуты в .NET ничего не делают. Они существуют только для того, чтобы другой код (или разработчики) мог смотреть на них позже. Подумайте об этом как о фантастическом комментарии.

С учетом этого вы можете написать свой атрибут следующим образом

public class AtomicAttribute : Attribute { } 

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

Учитывая, что С# является скомпилированным языком и заданы правила .NET CLR, теоретически существует 3 способа сделать это

  • Захватите компилятор С# и сделайте вывод другого кода, когда он увидит этот атрибут.
    Кажется, это было бы неплохо, но это просто невозможно прямо сейчас. Возможно, Roslyn проект может позволить это в будущем, но пока вы не можете этого сделать.

  • Напишите что-нибудь, что сканирует сборку .NET после того, как компилятор С# преобразует его в MSIL и изменит MSIL.
    В основном это PostSharp. Сканирование и переписывание MSIL затруднено. Существуют библиотеки, такие как Mono.Cecil, которые могут помочь, но это все еще очень сложная проблема. Это может также мешать отладчику и т.д.

  • Используйте API.NET Profiling для мониторинга программы во время ее запуска, и каждый раз, когда вы видите вызов функции с этим атрибутом, перенаправляйте его на другую функцию-обертку.
    Это, пожалуй, самый простой вариант (хотя это все еще очень сложно), но недостатком является то, что ваша программа теперь должна запускаться под профилировщиком. Это может быть хорошо на вашем ПК разработки, но это вызовет огромную проблему, если вы попробуете его развернуть. Кроме того, с таким подходом, вероятно, будет большой успех.

На мой взгляд, лучше всего создать функцию-оболочку, которая устанавливает транзакцию, а затем передать ей лямбда, которая выполняет фактическую работу. Вот так:

public static class Ext 
{
    public static void Atomic(Action action) 
    {
        using(var scope = new TransactionScope()) 
        {
            action();
            scope.Commit();
        }
    }
}

.....

using static Ext; // as of VS2015

public void Foo()
{
    Atomic(() => {
        // foo logic
    }
}

Притягательный термин компьютерной науки для этого Программирование более высокого порядка

Ответ 2

Атрибуты - это метаданные - все, что они есть.

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

Инструменты AOP, такие как PostSharp, читают такие метаданные, чтобы знать, что и где сплести аспекты в код.

Короче говоря, просто написать AtomicAttribute даст вам ничего - вам нужно будет передать скомпилированную сборку с помощью инструмента, который знает об этом атрибуте и сделает что-то для него, чтобы достичь АОП.

Ответ 3

Это не основная вещь. Никакой дополнительный код не запускается только потому, что у метода есть атрибут, поэтому код TransactionScope не существует.

Что вам нужно сделать, так это при отражении использования приложения при запуске приложений для каждого метода в каждом классе в вашей сборке и найти методы, отмеченные AtomicAttribute, а затем написать собственный прокси-сервер вокруг этого объекта. Затем каким-то образом получить все остальное, чтобы назвать ваш прокси вместо реальной реализации, возможно, используя инфраструктуру инъекции зависимостей.

Большинство фреймворков AOP делают это во время сборки. PostSharp, например, запускается после сборки VisualStudio. Он сканирует вашу сборку и перезаписывает код IL для включения прокси-серверов и перехватчиков AOP. Таким образом, сборка настроена на запуск, когда она запущена, но IL изменился с того, что вы изначально писали.

Ответ 4

Может быть, разрешить все объекты с помощью контейнера IoC? Вы можете настроить перехватчики для своих типов и в них проверить, украшен ли вызываемый метод этим атрибутом. Вы можете кэшировать эту информацию, чтобы вам не приходилось использовать отражение при каждом вызове метода.

Итак, когда вы это сделаете:

var something = IoC.Resolve<ISomething>();

something - это не объект, который вы реализовали, но прокси. В этом прокси-сервере вы можете делать все, что захотите, до и после вызова метода.