А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 30.11.04 11:27
Оценка:
Есть идея замутить сабж. Нечто типа Aspect#, только чтобы грамматика этого расширения укладывалась в грамматику C#.

Попробую описать основные идеи, которые появились как-то на досуге.

[Aspect("contains(@Name,'Test')")] // Это метаатрибут, в котором задается, к каким классам будет применена трансформация.
public class MyAspect
{
    [PointCut] // Это метаатрибут, который объявляет, что это шаблонный метод.
    public void MyMethod(int x, string s) // По сигнатуре метода можно получить фильтр для XPath, по которому выбираются методы, которые нужно трансформировать.
    {
        // Код, который нужно выполнить до вызова метода.
        MyMethod(x, s); // Это не рекурсивный вызов, а точка, куда нужно подставить потроха преобразуемого метода.
        // Код, который нужно выполнить после вызова метода.
    }
}


Допустим есть такой класс:

public class Test1
{
    public void MyTest1(int x, string s)
    {
        // Код матода.
    }
}


После преобразования получим нечто типа этого
public class Test1
{
    public void MyTest1(int x, string s)
    {
        // Код, который нужно выполнить до вызова метода.
        InternalMyTest1(x, s); // Это вместо вызова MyMethod(x, s); в определнии аспекта.
        // Код, который нужно выполнить после вызова метода.
    }

    private void InternalMyTest1(int x, string s)
    {
        // Код матода.
    }
}


Можно делать не только пре и пост обработку. Можно например обернуть все это в try...catch. Ну в общем что угодно...
Аналогичную обработку можно сделать и для свойств, полей, конструкторов.

Надеюсь, идею я изложил понятно. Прошу высказываться, кто что думает по этому поводу.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.12.04 20:21
Оценка: 15 (1)
Здравствуйте, gloomy rocker, Вы писали:

GR>Есть идея замутить сабж. Нечто типа Aspect#, только чтобы грамматика этого расширения укладывалась в грамматику C#.

...

GR>Можно делать не только пре и пост обработку. Можно например обернуть все это в try...catch. Ну в общем что угодно...

GR>Аналогичную обработку можно сделать и для свойств, полей, конструкторов.

GR>Надеюсь, идею я изложил понятно. Прошу высказываться, кто что думает по этому поводу.


Идея здравая, но делать для этого Aspect# не нужно. Это реализуется R#-ом уже сейчас. Нужно только сделать качественную реализацию соотвествующего правила.

Вот тут:
* Обзор современного состояния проекта, TODO и т.п.
Автор: VladD2
Дата: 30.10.04

* Мета-правило реализующиее паттерн Visitor
Автор: VladD2
Дата: 16.11.04

можно получить общее представление как это можно сделать.

В двух словах все выглядит так:
R# реализован как некий компилятор-препроцессор. Он запускается перед окончательной комплиляцией, парсит проект (путь к которому получает в качестве параметра), ищет мета-правали, выполняет их (те в свою очередь меняют AST как хотят), генерирует по преобразованному AST код на чистом C#.

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

Причем в принципе можно даже обходиться без пометок атрибутами. То что называется PointCut-ами и т.п. можно сделать XPath-запросами. Это намного гибче (хотя никто не мешает использовать и атрибуты).

Сделать это можно уже сейчас. Нужно только изучить как писать мета-правила.

Я сейчас делаю намного более сложные вещи. Проект RSharp.Rules генерирует код для самого R#-а. Например, вот из такого шаблона:
  [NodeName("class")]
  public class RTypeClass : RTypeUdt // обрати внимание! При генерации учитываются члены базового класса
  {
    public abstract RTypeClass NextPart
    {
      get; 
      set; 
    }
}

генерируется вот такой мудреный код:
[NodeName("class")]
public class RTypeClass : RTypeUdt
{
    #line default
    
    
    RTypeClass _nextPart;
    #line 25 "..\..\..\rsctest\codedom\types\RTypeClass.cs"
    
    public RTypeClass NextPart
    {
        get
        {
            #line default
            
            return _nextPart;
        }
        set
        {
            #line default
            
            if (value.Parent != null)
                _nextPart = ((RTypeClass)(value.Clone()));
            else
                _nextPart = value;
            value.Parent = this;
        }
    }
    [RSParser.CodeDom.Design.TypeID(TypeID.RTypeClass)]
    public override TypeID TypeID
    {
        get
        {
            return TypeID.RTypeClass;
        }
    }
    public override AstNode Clone()
    {
        RTypeClass clone = new RTypeClass();
        clone.BaseTypes.AddRange(this.BaseTypes);
        clone.Members.AddRange(this.Members);
        clone.TypeParameters.AddRange(this.TypeParameters);
        clone.CustomAttributeSections.AddRange(this.CustomAttributeSections);
        clone.Name = this.Name;
        clone.Modifiers = this.Modifiers;
        clone.StartLocation = this.StartLocation;
        clone.EndLocation = this.EndLocation;
        clone.FileName = this.FileName;
        {
            RTypeClass temp = this.NextPart;
            if (temp != null)
                clone.NextPart = ((RTypeClass)(temp.Clone()));
        }
        #line default
        
        return clone;
    }
    #line default
    
    public override IDomContainer GetChild(int index)
    {
        switch (index)
        {
        case 0:
            return this.BaseTypes;
        case 1:
            return this.Members;
        case 2:
            return this.TypeParameters;
        case 3:
            return this.CustomAttributeSections;
        case 4:
            return this.NextPart;
        }
        throw new ArgumentException("Illegal index.");
    }
    public override int GetChildCount()
    {
        return 5;
    }
    public override string[] GetChildNames()
    {
        return _childNames;
    }
    public override int[] GetWritableStringPropertyIndices()
    {
        return _writableStringPropertyIndices;
    }
    public override int[] GetStringPropertyIndices()
    {
        return _stringPropertyIndices;
    }
    public override string[] GetPropertyNames()
    {
        return _propertyNames;
    }
    
    private readonly static string[] _childNames = 
    {
        #line default
        "NextPart","BaseTypes","Members","TypeParameters","CustomAttributeSections"
    };
    #line default
    
    
    private readonly static string[] _propertyNames = 
    {
        #line default
        "Name","Modifiers","StartLocation","EndLocation","FileName","TypeCode",
    "NamespaceName","FullName","TypeID"
    };
    #line default
    
    
    protected new static int[] _stringPropertyIndices = 
    {
        #line default
        0,4,6,7
    };
    #line default
    
    
    protected new static int[] _writableStringPropertyIndices = 
    {
        #line default
        0,4
    };
    #line default
    
    public override void AcceptVisitor(IAstVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Это намного сложнее чем АОП, так как подставляемая реализация автоматически генерируется по определенному алгоритму. Так что сделать АОП вообще не сложная задача. Вот только у меня и так много проблем. Нужно доделать самогенерацию кода R#, а у меня на работе полный завал.

Так что есль есть решимость, желание и смелость, то милости просим... Я со своей соторы постараюсь помчь советом и ответить на любые вопросы.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 02.12.04 08:47
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Идея здравая, но делать для этого Aspect# не нужно. Это реализуется R#-ом уже сейчас. Нужно только сделать качественную реализацию соотвествующего правила.

Я в курсе. За проектом следил, правда пока в режиме read only. Про Aspect# я упомянул просто для того, чтобы определить желаемую функциональность.

VD>В двух словах все выглядит так:

VD>R# реализован как некий компилятор-препроцессор. Он запускается перед окончательной комплиляцией, парсит проект (путь к которому получает в качестве параметра), ищет мета-правали, выполняет их (те в свою очередь меняют AST как хотят), генерирует по преобразованному AST код на чистом C#.
У меня в данный момент стоит Visual C# Express Betta 1. К нему можно прикрутить этот препроцессор? Или нужна полноценная студия + VSIP?

VD>Таким образом ничто не мешает сделать правило которое с помощью XPath-запроса найдет все конструкции помеченные неким атрибутом, найдет аспект и интеллектуально скопирует аспектный код в нужные места.

То есть ты предлагаешь атрибутами помечать те объекты, к которым нужно применить трансформацию? У меня была идея сделать наоборот. В определнии аспекта указывать, какие классы нужно выбрать, и какие члены этого класса нужно трансформировать. Я уже немного покопался в RSParser и RSharp.Query. Реализовал преобразование, которое привел в первом посте.
Прежде чем двигаться дальше, хотелось бы обсудить общую концепцию — как опрелять этот аспект, где какие атрибуты ставить. И с точки зрения юзабилити и с точки зрения скорости преобразования.

VD>Причем в принципе можно даже обходиться без пометок атрибутами. То что называется PointCut-ами и т.п. можно сделать XPath-запросами. Это намного гибче (хотя никто не мешает использовать и атрибуты).

Я примерно так и думал. Для простых случаев(когда аспект не размечен атрибутами) XPath можно генерировать автоматом.
Например в аспекте есть метод
public int MyMethod(int x,string y)

Для него можно сгенерит XPath типа такого (относительно класса):
.//RMemberMethodImpl[.//RParameter/Type[1]/@Name='int' and .//RParameter/Type[2]/@Name='string']

Насчет названий нод могу наврать. И еще надо добавить условие на тип возвращаемого значения.

VD>Сделать это можно уже сейчас. Нужно только изучить как писать мета-правила.

Ок. Посмотрю исходники, потом задам вопросы.

VD>Это намного сложнее чем АОП, так как подставляемая реализация автоматически генерируется по определенному алгоритму. Так что сделать АОП вообще не сложная задача. Вот только у меня и так много проблем. Нужно доделать самогенерацию кода R#, а у меня на работе полный завал.

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

VD>Так что есль есть решимость, желание и смелость, то милости просим... Я со своей соторы постараюсь помчь советом и ответить на любые вопросы.

Желание есть, идеи тоже. Единственная проблема — время Могу заниматься этим в вяло текущем режиме дома по выходным, когда никто меня не хочет видеть
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[3]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.12.04 14:32
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>У меня в данный момент стоит Visual C# Express Betta 1. К нему можно прикрутить этот препроцессор?


Да. Я, правда, давно не проверял, но принципиальных проблем вроде бы нет. Проект при переводе на 2-ой Framework как раз на Экспрессе и переводился.

GR> Или нужна полноценная студия + VSIP?


Полноценная студия приятнее. Там больше фич. VSIP для основных возможностей вообще не нужен. Там есть пара начатых проектов использующих ВСИП, но они в основном пока не развиваются.

GR>То есть ты предлагаешь атрибутами помечать те объекты, к которым нужно применить трансформацию?


Я как бы о том, что делать можно как угодно. Атрибуты — это одно из средств разметки. В принципе многое можно сделать вообще без них. Как я уже говорил XPath-рулит (если кому не понятно, то XPath — это язык запросов позволяющий находить нужные участки кода (AST)).

GR> У меня была идея сделать наоборот. В определнии аспекта указывать, какие классы нужно выбрать, и какие члены этого класса нужно трансформировать. Я уже немного покопался в RSParser и RSharp.Query. Реализовал преобразование, которое привел в первом посте.


Гуд.

GR>Прежде чем двигаться дальше, хотелось бы обсудить общую концепцию — как опрелять этот аспект, где какие атрибуты ставить. И с точки зрения юзабилити и с точки зрения скорости преобразования.


ОК, я с удовольствием постараюсь разобраться в идеях/проблемах и выскажу свои мысли по этому поводу.

GR>Я примерно так и думал. Для простых случаев(когда аспект не размечен атрибутами) XPath можно генерировать автоматом.

GR>Например в аспекте есть метод
GR>
GR>public int MyMethod(int x,string y)
GR>

GR>Для него можно сгенерит XPath типа такого (относительно класса):
GR>
GR>.//RMemberMethodImpl[.//RParameter/Type[1]/@Name='int' and .//RParameter/Type[2]/@Name='string']
GR>

GR>Насчет названий нод могу наврать. И еще надо добавить условие на тип возвращаемого значения.

Кстати, учти, что XPath иногда может быть довольно накладным. Так что для часто применяемых случаев, возможно, нужно будет делать ручную оптимизацию. Но как и любую оптимизацию ее нужно делать после того как основные идеи заработают.

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


Я надеюсь, что мы и так получим реально полезный инструмент. Ну, да чем больше будет выбор готовых и универсальных мета-правил, тем более полезным будет R#. Так что я обоими руками "за".

GR>Желание есть, идеи тоже. Единственная проблема — время Могу заниматься этим в вяло текущем режиме дома по выходным, когда никто меня не хочет видеть


Ну, лучше что-то чем ничего.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 02.12.04 14:50
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Да. Я, правда, давно не проверял, но принципиальных проблем вроде бы нет. Проект при переводе на 2-ой Framework как раз на Экспрессе и переводился.

Ок. Тогда проблем нет.

VD>ОК, я с удовольствием постараюсь разобраться в идеях/проблемах и выскажу свои мысли по этому поводу.

Завтра постараюсь выложить то, что наваял. Для более детального обсуждения.

VD>Кстати, учти, что XPath иногда может быть довольно накладным. Так что для часто применяемых случаев, возможно, нужно будет делать ручную оптимизацию. Но как и любую оптимизацию ее нужно делать после того как основные идеи заработают.

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

VD>Я надеюсь, что мы и так получим реально полезный инструмент. Ну, да чем больше будет выбор готовых и универсальных мета-правил, тем более полезным будет R#. Так что я обоими руками "за".

Меня сама идея расширяемого языка радует
Так что попытаюсь и что-то свое внести
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[4]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 03.12.04 15:45
Оценка: 17 (1)
Выкладываю обещаное.

Код аспекта
using System;

namespace AOP.FrameWork
{
    [Aspect("contains(@Name,'Test')")]
    public class Aspect
    {
        public Aspect()
        {
        }

        [PointCut]
        public void MyMethod(int x, string y)
        {
            try
            {
                if(true)
                    MyMethod(x, y);
                else
                    MyMethod(x+1, y+"qqq");
                string s = string.Format("aaa {0}", 1);
            }
            catch (Exception e)
            {
                throw;
            }
        }
    }
}


Код, который будем преобразовывать
namespace AOP.Test
{
    public class Test1
    {
        public void MyMethod1(int x)
        {
            int z = x * x;
        }

        public void MyMethod2(int y, string s)
        {
            int z = y + s.Length;
        }
    }

    public class Test2
    {
        public int MyMethod1(int x)
        {
            return x * x;
        }

        public int MyMethod2(int y, string s)
        {
            return y + s.Length;
        }
    }
}


Результат преобразования.
namespace AOP.Test
{
    public class Test1
    {
        public void MyMethod1(int x)
        {
            int z = x * x;
        }
        public void InternalMyMethod2(int y, string s)
        {
            int z = y + s.Length;
        }
        [PointCut]
        public void MyMethod2(int x, string y)
        {
            try
            {
                if (true)
                    InternalMyMethod2(x, y);
                else
                    InternalMyMethod2(x + 1, y + "qqq");
                string s = string.Format("aaa {0}", 1);
            }
            catch(Exception e)
            {
                throw;
            }
        }
    }

    public class Test2
    {
        public int MyMethod1(int x)
        {
            return x * x;
        }
        public int MyMethod2(int y, string s)
        {
            return y + s.Length;
        }
    }
}


Код преобразователя.
#region Using directives

using System;
using System.Text;
using System.Xml.XPath;

using RSharp.Query;
using RSParser.Engine;
using RSParser.CodeDom;

#endregion

namespace AOP.FrameWork
{
    public class AspectCompiler
    {
        #region Конструктор.

        private string _aspectFileName;
        private string _targetFileName;

        public AspectCompiler(string aspectFileName, string targetFileName)
        {
            _aspectFileName = aspectFileName;
            _targetFileName = targetFileName;
        }

        #endregion

        #region Свойства

        private RCompileUnit _aspectCompileUnit;
        public RCompileUnit AspectCompileUnit
        {
            get
            {
                if (_aspectCompileUnit == null)
                {
                    Scanner scanner = new Scanner();
                    scanner.InitFromFile(_aspectFileName);
                    Parser parser = new Parser(scanner);
                    parser.Parse();
                    _aspectCompileUnit = parser.CompileUnit;
                }
                return _aspectCompileUnit;
            }
        }

        private RCompileUnit _targetCompileUnit;
        private RCompileUnit TargetCompileUnit
        {
            get
            {
                if (_targetCompileUnit == null)
                {
                    Scanner scanner = new Scanner();
                    scanner.InitFromFile(_targetFileName);
                    Parser parser = new Parser(scanner);
                    parser.Parse();
                    _targetCompileUnit = parser.CompileUnit;
                }
                return _targetCompileUnit;
            }
        }

        private string CompiledCode
        {
            get
            {
                RSParser.CodeDom.CS csVistor = new CS(false);
                TargetCompileUnit.AcceptVisitor(csVistor);
                return csVistor.ToString();
            }
        }

        #endregion

        #region Трансформация

        /// <summary>
        /// Перебор методов аспекта, поиск классов и методов, которые нужно преобразовать.
        /// </summary>
        /// <returns>Перобразованный код.</returns>
        public string ApplyTransformations()
        {
            string aspectXPath = "//class[.//attribute/@Name='Aspect']";
            string pointCutXPath = ".//RMemberMethodImpl[.//attribute/@Name='PointCut']";

            Navigator aspectCompileUnitNavigator = new Navigator(AspectCompileUnit);
            XPathNodeIterator aspectIterator = aspectCompileUnitNavigator.Select(aspectXPath);

            foreach (Navigator aspectNavigator in aspectIterator)
            {
                RTypeClass aspectClass = (RTypeClass)aspectNavigator.Current;

                Navigator targetCompileUnitNavigator = new Navigator(TargetCompileUnit);
                XPathNodeIterator targetClassIterator = targetCompileUnitNavigator.Select(GetClassXPath(aspectClass));
                foreach (Navigator targetClassNavigator in targetClassIterator)
                {
                    XPathNodeIterator pointCutIterator = aspectNavigator.Select(pointCutXPath);
                    foreach (Navigator pointCutNavigator in pointCutIterator)
                    {
                        string targetMethodXPath = GetMethodXPath((RMemberMethodImpl)pointCutNavigator.Current);
                        XPathNodeIterator targetIterator = targetClassNavigator.Select(targetMethodXPath);
                        foreach (Navigator targetNavigator in targetIterator)
                            ApplyTransformations((RMemberMethodImpl)targetNavigator.Current, (RMemberMethodImpl)pointCutNavigator.Current);
                    }
                }
            }

            return CompiledCode;
        }

        /// <summary>
        /// Здесь идет сама трансформация AST.
        /// </summary>
        /// <param name="targetMethod">Метод, который нужно преобразовать.</param>
        /// <param name="pointCut">Шаблонный метод.</param>
        private void ApplyTransformations(RMemberMethodImpl targetMethod, RMemberMethodImpl pointCut)
        {
            // Трансформация сводится к копированию метода из аспекта в целевой класс,
            // предварительно переименовав его, с последующей заменой некоторых кусков кода.
            RMemberMethodImpl copyOfPointCutMethod = (RMemberMethodImpl)pointCut.Clone();

            // В методах аспекта для указания точки входа в преобразуемый метод используется
            // рекурсивный вызов. Данный кусок кода меняет все эти вызовы на вызовы InternalXXX,
            // где XXX - имя преобразуемого метода.
            string replaceCallsXPath = string.Format("//*[@VariableName='{0}']", pointCut.Name);
            Navigator callsNavigator = new Navigator(copyOfPointCutMethod);
            foreach (Navigator callNavigator in callsNavigator.Select(replaceCallsXPath))
                ((RUnresolvedReferenceExpression)callNavigator.Current).VariableName = "Internal" + targetMethod.Name;

            // Реально целевой метод не меняется, а заменяется на свой враппер.
            copyOfPointCutMethod.Name = targetMethod.Name;
            targetMethod.Name = "Internal" + targetMethod.Name;

            ((RTypeMemberCollection)targetMethod.Parent).Add(copyOfPointCutMethod);
        }

        #endregion

        #region static методы.

        /// <summary>
        /// У аспекта должен быть атрибут Aspect, который может иметь параметр - фильтр.
        /// Если фильтр задан, то получим нечто типа //class[filter]
        /// </summary>
        /// <param name="aspectClass">Класс аспекта.</param>
        /// <returns>XPath для выбора классов, методы которых должны подвергнуться преобразованию.</returns>
        private static string GetClassXPath(RTypeClass aspectClass)
        {
            RAttributeDeclaration aspectAttributeDeclaration = aspectClass.CustomAttributes.Find("Aspect");
            if (aspectAttributeDeclaration == null)
                throw new Exception("Ожидался атрибут [Aspect]");

            switch (aspectAttributeDeclaration.Arguments.Count)
            {
                case 0:
                    return "//class";
                case 1:
                    RPrimitiveExpression filterExpression = (RPrimitiveExpression)aspectAttributeDeclaration.Arguments[0].Value;
                    return string.Format("//class[{0}]",filterExpression.Value.Trim('"'));
                default:
                    throw new Exception("У атрибута Aspect может быть только один параметр - XPath фильтр.");
            }
        }

        /// <summary>
        /// Используя сигнатуру метода в аспекте генерирует XPath, учитывая количество параметров,
        /// тип возвращаемого значения, типы параметров метода.
        /// </summary>
        /// <param name="pointCutMethod">Метод, определнный в аспекте, и содержимое котрого нужно скопировать в свой код.</param>
        /// <returns>XPath для выбора методов(относительно класса), которые енужно преобразовать.</returns>
        private static string GetMethodXPath(RMemberMethodImpl pointCutMethod)
        {
            int position = 0;

            StringBuilder methodFilterBuilder = new StringBuilder();
            methodFilterBuilder.AppendFormat(
                ".//RMemberMethodImpl[./ReturnType[TypeName='{0}']",
                ((RNonArrayTypeReference)pointCutMethod.ReturnType).TypeName);

            foreach (RParameterDeclarationExpression prm in pointCutMethod.Parameters)
            {
                methodFilterBuilder.AppendFormat(
                    " and .//RParameterDeclarationExpression[{0}]/Type/TypeName='{1}'",
                    ++position,
                    ((RNonArrayTypeReference)prm.Type).TypeName);
            }

            methodFilterBuilder.AppendFormat(" and count(.//RParameterDeclarationExpression)={0}]", position);
            return methodFilterBuilder.ToString();
        }

        #endregion
    }
}


Сразу вижу несколько проблем.
1. Выглядит несколько жутковато. Наверно можно упростить.
2. XPath может такой сгенериться, что можно будет пить чай, пока весь проект модифицируется.
3. Internal методы нужно делать private.
4. static методы пока не пробовал обрабатывать, наверно придется что-то дописывать для этого.
5. массивы и params не обрабатываются.
6. Из результирующего кода не удаляются метаатрибуты.
7. нельзя указать, что такой-то аргумент может быть одного из типов или любого типа.

Пункты 1 — 6 реализовать будет не сложно. Нужно просто кодить. А вот пункт 7 — это уже более интересно.
Здесь вопрос нужно ставить шире — как задавать PointCut-ы так чтобы это охватывало максимум потребностей.
То есть для начала нужно определить требования к этим PointCut-ам, а потом решать, как их описывать.
Я вижу следующие варианты:
1. Указать набор типов некоторого аргумента, или тип базового класса.
2. Указать, что после или до некоторого аргумента может идти сколько угодно аргументов.

Все это можно сделать через явное задание XPath в метаатрибутах, но хотелось бы минимизировать количество мест,
где он необходим.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[5]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.12.04 18:55
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Выкладываю обещаное.


GR>Код аспекта...


Откровенно говоря было бы лучше если примеры были менее надуманными. А то не ясно, разные "if (true)" — это приколы или в этом есть глубокий смысл.

GR>namespace AOP.FrameWork


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

GR>    [Aspect("contains(@Name,'Test')")]
GR>    public class Aspect


Атрибут не очень очевиден. Хорошо бы пояснить, что ты вкладывашь в "contains(@Name,'Test')".

GR>Код преобразователя.

...
GR>namespace AOP.FrameWork

Здесь уж точно нужно свое пространство имен. Например: RSharp.Aop. Или RSharp.AopFramework.

GR>    public class AspectCompiler
GR>    {

А сам преобразовательн нужно делать в качестве мета-правила. Для этго нужно реализовать интефейс IMetaRule. Как делать мета-правила я уже писал где-то радом. Плюс есть два примера:

RSharp\RSharp.Rules\AstNodeImplRule.cs
RSharp\RSharp.Rules\VisitorRule.cs


Сам код по позже погляжу более внимательно.

GR>Сразу вижу несколько проблем.

GR> 1. Выглядит несколько жутковато. Наверно можно упростить.

Если учесть, что это реализация АОП-а, то не так все страшно. Все же не часто найдешь технологию способную вот так вот с ходу реализовать компайл-тайм-АОП. Ну, да чем проще тем лучше. По позже гляну.

GR> 2. XPath может такой сгенериться, что можно будет пить чай, пока весь проект модифицируется.


А в чем проблема?

GR> 3. Internal методы нужно делать private.


Да уж. И надо бы делать проверку, что таких еще нет. Или давать более экзотические имена. Например, начинать их с символа "@". Его редко используют на практике, так что дублирование будет почти исключено.

GR> 4. static методы пока не пробовал обрабатывать, наверно придется что-то дописывать для этого.


Они отличаются только модификатором static. Так что если ты при поимке не учитывашь модификаторы (что вряд ли), то они тоже проскочат.

GR> 6. Из результирующего кода не удаляются метаатрибуты.


Для этого есть методы AstNode.DeleteAttribute, AstNode.DeleteAttributes. Так что прикрутить их удаление — это задача на пару минут.

GR> 7. нельзя указать, что такой-то аргумент может быть одного из типов или любого типа.


Можно Сделать так... Ввести некий атрибут, например, ParamPattern. В этом атрибуте определять имя типа который может меняться и масску поиска. Например:
[PointCut]

public void MyMethod(
    int x, 
    [Pattern(T = "string | System.Text.StringBuilder")]
    T y)
{
...

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

В паттерне можно или перечислять типы, или указывать базовый класс.

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

GR>Я вижу следующие варианты:

GR> 1. Указать набор типов некоторого аргумента, или тип базового класса.

Это я уже описал.

GR> 2. Указать, что после или до некоторого аргумента может идти сколько угодно аргументов.


А это уже наверно проще выразить в виде XPath-а.

GR>Все это можно сделать через явное задание XPath в метаатрибутах, но хотелось бы минимизировать количество мест,

GR>где он необходим.

По-моему, можно позволить использовать оба варианта. Для этого достатчно определить два варианта атрибутов. Тогда сложне случаи можно будет наворачивать на XPath-запросах, а более простые и стало быть более часто встречающиеся на упрощенном синтаксисе.

Например, можно делать так:
[PointCut]
[MatchAnyParameters]
[RequireParam(a = XPath("некторый XPath-запрос парамета"))]
[OptionalParam(b = XPath("другой XPath-запрос"))]
public void MyMethod()
{
...
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.12.04 18:55
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Меня сама идея расширяемого языка радует


Дык, о том и речь. Мы наш мы новый мир построим. В общем, мета-программирование рулит.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 06.12.04 09:12
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Откровенно говоря было бы лучше если примеры были менее надуманными. А то не ясно, разные "if (true)" — это приколы или в этом есть глубокий смысл.

Это скорее приколы. Просто некогда было писать что-то полезное, а пример должен был компилироваться обычным шарпом.

VD>Наверно пространство имен лучше назвать как-то по другому. Хотя опять же для абстрактного примера это не просто.

Было бы что называть — название придумаем

VD>
GR>>    [Aspect("contains(@Name,'Test')")]
GR>>    public class Aspect
VD>


VD>Атрибут не очень очевиден. Хорошо бы пояснить, что ты вкладывашь в "contains(@Name,'Test')".

Да, атрибуту наверно стоит дать более емкое название. Смысл такой: этот атрибут превращается в кусок XPath такого типа
//class[contains(@Name,'Test')]


VD>А сам преобразовательн нужно делать в качестве мета-правила. Для этго нужно реализовать интефейс IMetaRule. Как делать мета-правила я уже писал где-то радом. Плюс есть два примера:

VD>

RSharp\RSharp.Rules\AstNodeImplRule.cs
VD>RSharp\RSharp.Rules\VisitorRule.cs

Я вчера посмотрел на код AstNodeImplRule. Проблем с написание правила я не увидел, но для меня осталось не понятно, в какой момент и каким образом это правило применяется. На одной из встречь юзер группы ты рассказывал это, но я уже успел забыть. Напомни плиз, или ссылочки кинь, где это описано.

VD>Если учесть, что это реализация АОП-а, то не так все страшно. Все же не часто найдешь технологию способную вот так вот с ходу реализовать компайл-тайм-АОП. Ну, да чем проще тем лучше. По позже гляну.

Это скорее мелочи. Украшательства буду делать потом, когда будет что украшать

GR>> 6. Из результирующего кода не удаляются метаатрибуты.

VD>Для этого есть методы AstNode.DeleteAttribute, AstNode.DeleteAttributes. Так что прикрутить их удаление — это задача на пару минут.
С этим у меня какие-то проблемы были, но тогда пример еще был не совсем рабочий. Надо еще раз попробовать.

GR>> 7. нельзя указать, что такой-то аргумент может быть одного из типов или любого типа.

VD>Можно Сделать так... Ввести некий атрибут, например, ParamPattern. В этом атрибуте определять имя типа который может меняться и масску поиска. Например:
VD>
VD>[PointCut]

VD>public void MyMethod(
VD>    int x, 
VD>    [Pattern(T = "string | System.Text.StringBuilder")]
VD>    T y)
VD>{
VD>...
VD>

VD>Ну, и использовать в коде аспекта имя этого паттерна. А при поиске делать более сложную обработку.
VD>В паттерне можно или перечислять типы, или указывать базовый класс.
Как вариант можно попробовать.

VD>Так же можно обдумать возможность использования в качестве описания паттенра синтаксис дженериков. В принципе он тут может подойди, но ведь сами дженерик-методы тоже должны обрабатываться, да и наверно аспекты могут быть дженериковыми. Так что тут нжно хорошенько подумать.

Что-то мне кажется, что без косяков тут не обойдется. Думаю, лучше мухи — отдельно, котлеты — отдельно.

GR>> 2. Указать, что после или до некоторого аргумента может идти сколько угодно аргументов.

VD>А это уже наверно проще выразить в виде XPath-а.
Кроме того, здесь вырисовывается еще один косяк. Нужно еще как-то описвать точки входа в преобразуемый метод. В моем примере было все просто. Точка входа выглядела как рекурсивный вызов, а при трансформации название вызываемого метода просто менялось на Internal***. Теперь количество и типы параметров заранее не известны, и такой подход не прокатит. Кроме того, допустим я хочу написать аспект MethodCallLogger, который будет в файл записывать что-то типа такого
Метод: MyMethod
    Аргумент: int x = 10
    Аргумент: string s = "test"

Очевидно, что такой аспект должен уметь работать с любыми методами, не зависимо от количества и типов аргументов. А в данной реализации шаблоны статические. То есть получется, что к шаблонам тоже нужно применять какие-то метаправила (правило генерится по проавилу, а потом уже применяется к реальному коду). Возможно, я все усложняю. Может есть другие идеи?

VD>По-моему, можно позволить использовать оба варианта. Для этого достатчно определить два варианта атрибутов. Тогда сложне случаи можно будет наворачивать на XPath-запросах, а более простые и стало быть более часто встречающиеся на упрощенном синтаксисе.


VD>Например, можно делать так:

VD>
VD>[PointCut]
VD>[MatchAnyParameters]
VD>[RequireParam(a = XPath("некторый XPath-запрос парамета"))]
VD>[OptionalParam(b = XPath("другой XPath-запрос"))]
VD>public void MyMethod()
VD>{
VD>...
VD>

Опять же проблема с описанием точки входа в преобразуемый метод.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[7]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.12.04 21:35
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Было бы что называть — название придумаем


+1

GR>Да, атрибуту наверно стоит дать более емкое название. Смысл такой: этот атрибут превращается в кусок XPath такого типа

GR>
GR>//class[contains(@Name,'Test')]
GR>


Так, а зачем он нужен? Ты же вроде по имени метода подключаться хотел. Или "contains(@Name,'Test')" — это фильтр по классу?

GR>Я вчера посмотрел на код AstNodeImplRule. Проблем с написание правила я не увидел, но для меня осталось не понятно, в какой момент и каким образом это правило применяется. На одной из встречь юзер группы ты рассказывал это, но я уже успел забыть. Напомни плиз, или ссылочки кинь, где это описано.


Дык я же уже приводил ссылку (Re[3]: Первое мета-правило
Автор: VladD2
Дата: 02.11.04
) в ней вроде все описано.

GR>...Что-то мне кажется, что без косяков тут не обойдется. Думаю, лучше мухи — отдельно, котлеты — отдельно.


Возможно... Тут нужно все как следует продумывать.

GR>>> 2. Указать, что после или до некоторого аргумента может идти сколько угодно аргументов.

VD>>А это уже наверно проще выразить в виде XPath-а.
GR>Кроме того, здесь вырисовывается еще один косяк. Нужно еще как-то описвать точки входа в преобразуемый метод. В моем примере было все просто. Точка входа выглядела как рекурсивный вызов, а при трансформации название вызываемого метода просто менялось на Internal***. Теперь количество и типы параметров заранее не известны, и такой подход не прокатит. Кроме того, допустим я хочу написать аспект MethodCallLogger, который будет в файл записывать что-то типа такого
GR>
GR>Метод: MyMethod
GR>    Аргумент: int x = 10
GR>    Аргумент: string s = "test"
GR>

GR>Очевидно, что такой аспект должен уметь работать с любыми методами, не зависимо от количества и типов аргументов. А в данной реализации шаблоны статические. То есть получется, что к шаблонам тоже нужно применять какие-то метаправила (правило генерится по проавилу, а потом уже применяется к реальному коду). Возможно, я все усложняю. Может есть другие идеи?

Честно говоря не думаю, что будет особый интерес работать с низвестными на момент описания аспектного метода параметрами, но если что всегда можно ввести некий (подразумеваемый) список параметров текущего метода. Например, так:
... SomeMethod(...)
{
    ...
    Log(Meta.Method.Name);
    Log(Meta.Method.Argumens);
    ...
}

ну, такая заранее известная переменная в которой бдет содержаться список параметров метода. В инструментированном коде она может быть заменена на следующий код:
... SomeMethod(int i, string s)
{
    ...
    Log("SomeMethod");
    object[] @MethodArgumens = new object[] { i , s };
    Log(@MethodArgumens);
    ...
}

ну, или нечто в этом духе. Например, можно ввести некую конструкцию которая будет повторяться для каждого аркумента:
... SomeMethod(...)
{
    ...
    Log(Meta.Method.Name);
    Meta.ForEach(Meta.Method.Argumens, param, Log(param));
    ...
}

которая развернется после инструментирования метода в:
... SomeMethod(int i, string s)
{
    ...
    Log("SomeMethod");
    Log(i);
    Log(s);
    ...
}

ну, то-есть ввести мета-конструкцию которая развернется более чем в одну строку кода. Ну, а уже сам вызываемый метод (в данном случае Log()) будет перегружен для всех встречающихся типов. В принципе такие вещи не совсем АОП, но это даже круче. Это настоящее мета-программирование. Именно ради этого я и затеевал весь этот R# .

VD>>Например, можно делать так:

VD>>
VD>>[PointCut]
VD>>[MatchAnyParameters]
VD>>[RequireParam(a = XPath("некторый XPath-запрос парамета"))]
VD>>[OptionalParam(b = XPath("другой XPath-запрос"))]
VD>>public void MyMethod()
VD>>{
VD>>...
VD>>

GR>Опять же проблема с описанием точки входа в преобразуемый метод.
Погоди, ты же по сути оборачиваешь метод. Точкой входа будут начало метода, конец метода (или return-ы). Или ты об поиске методов с назличными аргументами? Паттернами ты как раз их и будешь определять.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 07.12.04 15:49
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Так, а зачем он нужен? Ты же вроде по имени метода подключаться хотел. Или "contains(@Name,'Test')" — это фильтр по классу?

Это фильтр по классу. А подключаться я хотел в по сигнатуре метода и предоставить возможность фильтрации по названию.
Вполне возможно, что эта функциональность будет не нужна. Просто если она делается легким движением руки, то почему это сделать?

VD>Дык я же уже приводил ссылку (Re[3]: Первое мета-правило
Автор: VladD2
Дата: 02.11.04
) в ней вроде все описано.

Ок. Видимо я ее пропустил
Я вчера немного rsc и RSharp.Compiler подебажил. Вроде прояснилось, что к чему.

GR>>...Что-то мне кажется, что без косяков тут не обойдется. Думаю, лучше мухи — отдельно, котлеты — отдельно.

VD>Возможно... Тут нужно все как следует продумывать.
Придется подождать, когда наступит озарение

VD>Честно говоря не думаю, что будет особый интерес работать с низвестными на момент описания аспектного метода параметрами, но если что всегда можно ввести некий (подразумеваемый) список параметров текущего метода.

Скорее всего в 80% случаев так и будет. Но, как показывает практика, на 20% функциональность уходит 80% ресурсов. Для начала в любом случае надо реализовать базовую функилнальность.

VD>ну, то-есть ввести мета-конструкцию которая развернется более чем в одну строку кода. Ну, а уже сам вызываемый метод (в данном случае Log()) будет перегружен для всех встречающихся типов. В принципе такие вещи не совсем АОП, но это даже круче. Это настоящее мета-программирование. Именно ради этого я и затеевал весь этот R# .

Что-то подобное и в моей голове вертелось... Думаю, по мере реализации, я более детально разберусь в R#, и что-нить придумаю.

GR>>Опять же проблема с описанием точки входа в преобразуемый метод.

VD>Погоди, ты же по сути оборачиваешь метод. Точкой входа будут начало метода, конец метода (или return-ы). Или ты об поиске методов с назличными аргументами? Паттернами ты как раз их и будешь определять.
Вот тут во избежание не понимани друг друга остановлюсь подробней.
Допустим был такой класс
public class TestClass
{
    public int TestMethod(string s)
    {
        // Код класса.
    }
}


В процессе преобразования получим чо-то типа
public class TestClass
{
    // Просто переименованный метод.
    private int @TestMethod(string s)
    {
        // Код класса.
    }

    // Это AOP обертка.
    public int TestMethod(string s)
    {
        // Код аспекта.
        @TestMethod(string s) // Вот это место я называл "точкой входа".
        // Код аспекта.
    }
}


Теперь если вернуться к рассмотрению случая, когда сигнатура преобразуемого метода задается не четко, возникает вопрос, как в коде аспекта описать "точку входа"? То есть ее тоже придется задавать не четко. Причем код с точки зрения обычного C# должен быть корректным.
Здесь можно поступить так:
1. раз уж нам удалось корректно определить шаблонный метод в аспекте, то его можно и вызвать корректно.
2. используем все ту же рекурсию для объявления этих "точек входа".
3. при трансформации каждого конкретного метода корретируем "точки входа", зная сигнатуру трансформируемого метода.
4. сигнатуру "AOP обертки" конечно тоже нужно будет подправить.

З.Ы. Постараюсь в ближайшее время свой пример реализовать в виде метапраила с учетом последних появившихся идей.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[9]: А не замутить ли AOP на основе R#?
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.12.04 16:36
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Теперь если вернуться к рассмотрению случая, когда сигнатура преобразуемого метода задается не четко, возникает вопрос, как в коде аспекта описать "точку входа"? То есть ее тоже придется задавать не четко. Причем код с точки зрения обычного C# должен быть корректным.

Хм. А может, воспользоваться встроенным в шарп способом описывать "нечеткие сигнатуры"?
Примерно вот так:
public class TestClass
{
    public int TestMethod(int x, string s)
    {
        // Код класса.
    }
        public int TestMeth2(params object[] q)
        {
        // Код класса.
        }
}

после обработки вот таким аспектом:
[Aspect("contains(@Name,'Test')")] .
public class MyAspect
{
    [PointCut] 
    public int MyMethod
         [ExpandedMatch] // метаатрибут, который говорит о необходимости расширить матчинг следующего параметра. Без него поймается TestMeth2, а не TestMethod.
         params object[] pars)
    {
        Log.Write("Going to call MyMethod with these param values: ", pars);
        int i = MyMethod(pars); // Это не рекурсивный вызов, а точка, куда нужно подставить потроха преобразуемого метода.
        Log.Write("Called MyMethod with these param values: ", pars);
                return i;
    }
}

Превратится в:
public class TestClass
{
    private int @TestMethod(int x, string s)
        {
        // Код класса.
        }
    public int TestMethod(int x, string s)
    {
          object[] pars = new object[]{x, s};
        Log.Write("Going to call MyMethod with these param values: ", pars);
        int i = @TestMethod(x, s); 
        Log.Write("Called MyMethod with these param values: ", pars);
                return i;
    }
        public int TestMeth2(params object[] q)
        {
        // Код класса.
        }
}

Практически все приходящие мне на ум вариации в сигнатурах, которые может захотеться отлавливать, покрываются этим паттерном. При этом весь код остается валидным сишарпом.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 08.12.04 08:50
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Хм. А может, воспользоваться встроенным в шарп способом описывать "нечеткие сигнатуры"?

S>Примерно вот так:
Здесь подразумевается приведенный вами код.


У меня была аналогичная идея, только я не додумался как отличить нечеткую сигнатуру от четкой, когда используется params.
Похоже, дополнительный атрибут [ExpandedMatch] эту проблему решает. Я сейчас в свободное от безделья время переписываю приведенный мною пример как положено. Т.е. реализую IMataRule. Постараюсь и эту функциональность добавить.

S>Практически все приходящие мне на ум вариации в сигнатурах, которые может захотеться отлавливать, покрываются этим паттерном. При этом весь код остается валидным сишарпом.

Есть еще один нюанс. Допустим, нужно выбрать только те методы, у котороых 2-ой аргумент — int, а 5-ый — string. Остальные аргументы а кода аспекта не используются (не анализируются). Для задания таких паттернов наверно надо будет ввести еще один мета атрибут [DerivedTypeMatch], который будет указывать, что помеченный аргумент должен быть либо указанного типа, либо типа, производного от указанного. Соответственно, если указанный тип будет object, то фильтр по этому аргументу отсутствует.
Поясню примерами.
[Aspect]
public class MyAspect
{
        public void MyMethod
        (
            [DerivedTypeMatch] object obj,      // obj может быть любого типа
            [DerivedTypeMatch] XmlReader reader // reader может быть XmlNodeReader, XmlTextReader, XmlValidatingReader и т.д. 
        )
        {
            // Код аспекта.
        }    
        
}


Что-то мне вдруг вспомнился термин из функциональных языков — pattern matching. Я в функциональных языках пока чайник, может оттуда можно какие-то идеи позаимствовать...
Но это скорее в философию программирования

И еще я подумал, что эти два метаатрибута можно объединить. Получится так:
    public int MyMethod
    (
        [ExpandedMatch] params string[] pars // 1 или более аргументов типа string или производных от string. Сомнительно, что такой паттрен будет полезен, но все таки...
    )
    {
        // Код аспекта
    }

    public int MyMethod
    (
        [ExpandedMatch] string str // 1 аргумент типа string или производный от string.
    )
    {
        // Код аспекта
    }


И еще одно — фокус с params прокатит только для последнего аргумента. Но этого, я думаю, будет вполне достаточно.

Эээ... только сейчас подумал, а зачем нам вообще params? Разве не достаточно синтаксиса массивов и метаатрибута [ExpandedMatch]?
Эти паттерны будут означать то же самое:
    public int MyMethod
    (
        [ExpandedMatch] string[] pars // 1 или более аргументов типа string или производных от string. Сомнительно, что такой паттрен будет полезен, но все таки...
    )
    {
        // Код аспекта
    }

    public int MyMethod
    (
        [ExpandedMatch] string str // 1 аргумент типа string или производный от string.
    )
    {
        // Код аспекта
    }
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[11]: А не замутить ли AOP на основе R#?
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.12.04 09:22
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Есть еще один нюанс. Допустим, нужно выбрать только те методы, у котороых 2-ой аргумент — int, а 5-ый — string. Остальные аргументы а кода аспекта не используются (не анализируются). Для задания таких паттернов наверно надо будет ввести еще один мета атрибут [DerivedTypeMatch], который будет указывать, что помеченный аргумент должен быть либо указанного типа, либо типа, производного от указанного. Соответственно, если указанный тип будет object, то фильтр по этому аргументу отсутствует.

Ясно. В принципе подходит.

GR>[c#]

GR> public int MyMethod
GR> (
GR> [ExpandedMatch] params string[] pars // 1 или более аргументов типа string или производных от string. Сомнительно, что такой паттрен будет полезен, но все таки...
GR> )
GR> {
GR> // Код аспекта
GR> }
Не, не пойдет. Я бы от [ExpandedMatch] params MyType[] pars ожидал 1 атрибут типа MyType[] или MyDerivedType[]. В отличие от дефолтного екзакт матчинга. Парамз как раз позволяет отличить "произвольное" количество однотипных атрибутов от известного.

GR>И еще одно — фокус с params прокатит только для последнего аргумента. Но этого, я думаю, будет вполне достаточно.

Более чем. Иначе будет трудно управлять нежелательными матчами.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 08.12.04 11:43
Оценка:
Попробую подвести итог.

        // Паттерны по умолчанию.
        public static void Test1(int x,string s,
            string[] pars){} // Один параметр типа string[]. 

        public static void Test2(int x,string s,
            params string[] pars){} // Один параметр типа params string[]

        // Применение [ExpandedMatch]
        public static void Test3(int x,string s,
            [ExpandedMatch] string[] pars){} // Один параметр типа params string[]. [ExpandedMatch] игнорируется.

        public static void Test4(int x,string s,
            [ExpandedMatch] params string[] pars){} // 1 или больше параметров типа string

        // Применение [DerivedTypeMatch]
        public static void Test5(int x,string s,
            [DerivedTypeMatch] MyType[] pars){} // Один параметр типа MyType[] или DerivedMyType[].

        public static void Test6(int x,string s,
            [DerivedTypeMatch] params MyType[] pars){} // Один параметр типа params MyType[] или params DerivedMyType[].

        // Комбинированное применение [ExpandedMatch] и [DerivedTypeMatch]
        public static void Test7(int x,string s,
            [ExpandedMatch, DerivedTypeMatch] MyType[] pars){} // Один параметр типа MyType[] или DerivedMyType[]. [ExpandedMatch] игнорируется.

        public static void Test8(int x,string s,
            [ExpandedMatch, DerivedTypeMatch] params MyType[] pars){} // 1 или больше параметров типа MyType или DerivedMyType.


Кажется ничего не забыл...
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[11]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.12.04 22:32
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

GR>Что-то мне вдруг вспомнился термин из функциональных языков — pattern matching. Я в функциональных языках пока чайник, может оттуда можно какие-то идеи позаимствовать...

GR>Но это скорее в философию программирования

Это точно сюда Декларативное программирование.

А то что у тебя выресовывается действительно на него похоже. Вот только ПМ рассчитан на подбор вызова метода по значению его параметров, а у тебя подбор мета-описания вызва метода по типу параметров. Так что это скорее мета-перегрузка.

Вот только, а что если захочится найти метод с иным положением параметров? Ну, или расположением (т.е. когда между параметрами может быть случайное количество незначащих (неинтересных) параметров).

GR>И еще я подумал, что эти два метаатрибута можно объединить. Получится так:

GR>
GR>    public int MyMethod
GR>    (
GR>        [ExpandedMatch] params string[] pars // 1 или более аргументов типа string или производных от string. Сомнительно, что такой паттрен будет полезен, но все таки...
GR>    )
GR>    {
GR>        // Код аспекта
GR>    }

GR>    public int MyMethod
GR>    (
GR>        [ExpandedMatch] string str // 1 аргумент типа string или производный от string.
GR>    )
GR>    {
GR>        // Код аспекта
GR>    }
GR>


А если нужно не только строки? Ну, скажем целые и строки? Ведь параметр помеченны модификатором params может быть только один. Тогда уж лучше без него. Мета-атрибута в принципе достаточно.

В общем, мне кажется в аспектном метода лучше просто перечислять список параметров с которым ты собирашся работать. А уже целевые методы находить с помощью атрибутов привязыки. Например так:
    public int MyMethod
    (
        [Match("XPath-запрос 1")] string[] pars1,
        [Match("XPath-запрос 2")] int[]    pars2,
        [Match("XPath-запрос 2")] Xxx[]    pars3
    )
    {
        // Код аспекта
    }


GR>И еще одно — фокус с params прокатит только для последнего аргумента. Но этого, я думаю, будет вполне достаточно.


Вряд ли. Это не универсальное решение.

GR>Эээ... только сейчас подумал, а зачем нам вообще params?


Во-во.

GR> Разве не достаточно синтаксиса массивов и метаатрибута [ExpandedMatch]?


Более чем. "Expanded" тоже лишнее.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[13]: А не замутить ли AOP на основе R#?
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.12.04 22:32
Оценка:
Здравствуйте, gloomy rocker, Вы писали:

Игнорировать ничего не нужно. Это плохой дизайн.

Да и опять же те же вопросы...

А если нужно отловить два списка параметров?
А если нужно игнорировать некоторое количество параметров между значимыми?
А если значимые параметры могут идти не в одинаковом порядке?
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[12]: А не замутить ли AOP на основе R#?
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.12.04 06:32
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>В общем, мне кажется в аспектном метода лучше просто перечислять список параметров с которым ты собирашся работать. А уже целевые методы находить с помощью атрибутов привязыки. Например так:

VD>
VD>    public int MyMethod
VD>    (
VD>        [Match("XPath-запрос 1")] string[] pars1,
VD>        [Match("XPath-запрос 2")] int[]    pars2,
VD>        [Match("XPath-запрос 2")] Xxx[]    pars3
VD>    )
VD>    {
VD>        // Код аспекта
VD>    }
VD>

Это хорошо. Но как ты собираешься использовать эти атрибуты в коде аспекта? Они ведь нужны не только для того, чтобы найти метод с нужной сигнатурой.
Ты не мог бы проиллюстрировать этот аспект хотя бы двухстрочным примером? А то мне кажется, что это суперуниверсальное решение будет трудновато использовать.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 09.12.04 12:53
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Игнорировать ничего не нужно. Это плохой дизайн.

Эт точно. Каждая конструкция должна иметь значение, причем единственное.

VD>Да и опять же те же вопросы...

VD>А если нужно отловить два списка параметров?
VD>А если нужно игнорировать некоторое количество параметров между значимыми?
VD>А если значимые параметры могут идти не в одинаковом порядке?
Согласен с замечаниями. Вот только более универсальное решение может оказаться не очень удобным.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Re[12]: А не замутить ли AOP на основе R#?
От: gloomy rocker Россия  
Дата: 09.12.04 12:53
Оценка: 16 (1)
Здравствуйте, VladD2, Вы писали:

VD>А то что у тебя выресовывается действительно на него похоже. Вот только ПМ рассчитан на подбор вызова метода по значению его параметров, а у тебя подбор мета-описания вызва метода по типу параметров. Так что это скорее мета-перегрузка.

Мдааа... скоро мы тут столько терминов придумаем, что придется специальный словарь составлять

VD>Вот только, а что если захочится найти метод с иным положением параметров? Ну, или расположением (т.е. когда между параметрами может быть случайное количество незначащих (неинтересных) параметров).

Меня те же мысли мучиют

VD>А если нужно не только строки? Ну, скажем целые и строки? Ведь параметр помеченны модификатором params может быть только один. Тогда уж лучше без него. Мета-атрибута в принципе достаточно.

Тогда XPath нас спасет.

VD>В общем, мне кажется в аспектном метода лучше просто перечислять список параметров с которым ты собирашся работать. А уже целевые методы находить с помощью атрибутов привязыки. Например так:

VD>
VD>    public int MyMethod
VD>    (
VD>        [Match("XPath-запрос 1")] string[] pars1,
VD>        [Match("XPath-запрос 2")] int[]    pars2,
VD>        [Match("XPath-запрос 2")] Xxx[]    pars3
VD>    )
VD>    {
VD>        // Код аспекта
VD>    }
VD>

К стати, есть же какая-то кастом функция IsInheritedFrom. Она тоже неплохо впишется в XPath запрос.

GR>>И еще одно — фокус с params прокатит только для последнего аргумента. Но этого, я думаю, будет вполне достаточно.

VD>Вряд ли. Это не универсальное решение.
А у твоего варианта есть такой недостаток. Если уж один из аргументов помечен атрибутом [Match("XPath")], то и все остальные тоже должны быть помечены аналогичными атрибутами, т.к. в данном случае позиция аргумента не имеет значения, а PointCut определяется XPath-ами и типами аргументов. Получается, что надо поддерживать два синтаксиса — простой и сложный.

К стати, простой я вчера таки реализовал, только у меня сейчас сорцов с собой нету. Терерь во весь рост становится другая проблема(я о ней уже раньше начинал писать). Совершенно точно будут ситуации, когда одна строка кода аспекта должна будет развернуться в несколько строк реального кода. Например в том же примере с логированием вызовов. Конструктивных идей на этот счет у меня пока нет. Нужны какие-то хитрые конструкции, которые перед применением правла к конкретному методу будут разворачиваться в что-то более сложное.
Что могло бы пригодиться:
1. Meta.AOP.CurrentMetod
.Name
.ArgumentsCollection
.ReturnValue
.CustomAttributesCollection
.Modyfiers
...
2. Meta.AOP.ForEach
Думаю, названия сами за себя говорят — что это такое и зачем оно нужно. Если кратко, то из кода аспекта нужно иметь доступ к информации о методе, который преобразуется в данный момент. Для кадого преобразованного метода одна и та же конструкция в аспекте должна раскрываться в разный код. У кого какие идеи и предложения на эту тему?

Может такие куски кода можно помечать так?
#region Meta.ID(ForEachArgument)
    Console.WriteLine("Аргумент {0} = {1}", Argument.Name, Argument.Value);
#endregion
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Скука — двигатель прогресса.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.