Как сделать так, чтобы атрибуты метода базового класса применялись в унаследованном классе? - PullRequest
3 голосов
/ 27 июля 2011

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


Можно ли как-то определить свой атрибут, чтобы, если он определен в методе, который переопределен, атрибут все еще применяется?

Причина, по которой я спрашиваю, состоит в том, что у меня есть атрибут, который вводит некоторое поведение в метод, но поведение не применяется, когда метод вызывается, как в любом из случаев в дочернем классе, и я хотел бы, чтобы он был,

class BaseClass
{
    [MyThing]
    virtual void SomeMethod()
    {
        // Do something fancy because of the attribute.
    }
}

class ChildClass
{
    override void SomeMethod()
    {
        // Fancy stuff does happen here too...
        base.SomeMethod();
    }

    void AnotherMethod()
    {
        // ...but not here. And I'd like it to =(
        base.SomeMethod();
    }
}

Атрибут определен следующим образом:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MyThingAttribute : Attribute

Текущий код для поиска методов с атрибутом следующий:

var implementation = typeof(TheTypeWereCurrentlyInvestigating);
var allMethods = (from m in implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
                  let attribs = (TransactionAttribute[]) m.GetCustomAttributes(typeof (TransactionAttribute), true)
                  where attribs.Length > 0
                  select Tuple.Create(m, attribs.Length > 0 ? attribs[0] : null)).ToList();

Я не сделал 'Я не могу написать эту часть, и я не могу сказать, что я на 100% от того, что делает каждая ее часть ... Но сейчас мы можем предположить, что я контролирую весь задействованный код.(Это проект с открытым исходным кодом, так что я могу, по крайней мере, создать свою собственную версию и представить патч владельцам проекта ...)

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


ОБНОВЛЕНИЕ:

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

Что я сделал:
Я создал тестовый класс, в котором есть один метод, украшенныйатрибут и который вызывает метод Assert, который проверяет, что поведение было введено правильно (то есть, что есть транзакция).Затем я создал пару классов, которые наследуют этот тестовый класс, чтобы увидеть, в каких случаях все работает так, как я ожидаю.

Что я нашел:
Вызывая метод тестанепосредственно в тестовом классе и из различных методов дочерних классов я обнаружил следующее о том, что работает, а что нет:

Method called                 Access modifiers     Does it work?
*************                 ****************     *************
SomeMethod() on base class*   N/A                  Yes
OtherMethod() on child        neither              NO <-- headache!
OtherMethod() on child        hiding (new)         No
SomeMethod() on child         hiding (new)         No
OtherMethod() on child        overrides            No
OtherMethod() on child*       overrides            Yes
SomeMethod() on child         overrides            Yes

Во всех случаях, кроме одного, отмеченного *, base.SomeMethod() был вызван из метода, примененного в тесте.В первом случае был вызван тот же метод, но непосредственно из теста, поскольку дочерний класс не задействован.Во втором случае (из отмеченных *) был вызван метод переопределения, то есть this.SomeMethod(), так что это действительно эквивалентно последнему случаю.Я не использую никаких избыточных квалификаторов, поэтому в этом методе вызов просто SomeMethod().

Что я хочу:
Это случай с пометкой "головная боль", что я действительнохочу решить;как внедрить поведение в базовый класс, хотя я вызываю его из своего дочернего класса.

Причина, по которой мне нужен этот конкретный случай, заключается в том, что я использую этот шаблон в хранилище, где базовый класс определяет метод Save(T entity), украшенный атрибутом Transaction.В настоящее время я должен переопределить этот метод, просто чтобы получить оркестровку транзакции, что делает невозможным изменение типа возвращаемого значения;в базовом классе это void, но в моей реализации я бы хотел сделать его Error<T>.Это невозможно при переопределении, и, поскольку я не могу решить проблему, назвав метод по-другому, я в растерянности.

Ответы [ 3 ]

0 голосов
/ 27 июля 2011

Не могу пройти через это.Не могу пойти под это.Должен обойти это.

Учитывая ваши обстоятельства, как я их понимаю:

  • Невозможно применить существующий атрибут для добавления фиксации/ rollback: он никогда не будет откатываться, потому что вы сами ловите исключения в AnotherMethod().
  • Вам нужна инъекция коммита / отката в AnotherMethod().

Я подозреваю, что TransactionAttribute упаковывает тело метода в блок try-catch, преобразуя это (псевдокод):

public void SomeMethod() {
    DoStuff();
}

в нечто подобное (псевдокод, и очень упрощенно):

public void SomeMethod() {
    transaction.Begin();
    try {
        DoStuff();
        transaction.Commit();
    }
    catch {
        transaction.Rollback();
    }
}

Имея это в виду, вы можете применить TransactionAttribute к AnotherMethod() и повторно выдать исключения, которые вы ловите:

[TransactionAttribute]
public void AnotherMethod() {
    try {
        DoStuff();
    }
    catch (Exception ex) {
        //deal with exception
        throw;
    }
}

Если это невозможно -например, если вам нужна только часть поведения, которое внедряет TransactionAttribute, то вам, вероятно, придется создать новый TransactionAttribute, который внедряет поведение, которое вы хотите внедрить.Возможно, он ищет блоки try-catch и размещает коммиты и откаты в соответствующих местах, но это может быть сложнее, чем в текущей версии.

0 голосов
/ 27 июля 2011

Застрелен в темноте, но ...

Я предполагаю, что поведение транзакции внедряется контейнером IoC, и что оно делает это путем создания прокси при разрешении ChildClass. Поэтому код транзакции выполняется до \ после ChildClass.SomeMethod через прокси. Я предполагаю, что поведение, которое вы видите, заключается в том, что на BaseClass.SomeMethod не происходит внедрения кода, поэтому вызов его из ChildClass.AnotherMethod не требует каких-либо инъекций прокси-кода, он просто проходит.

Если это так, вы можете использовать композиционный шаблон и инъекцию BaseClass для решения проблемы.

Если вы разрешите следующий класс через свой контейнер, он вставит прокси BaseClass, который имеет соответствующий код до \ после транзакции для метода BaseClass.SomeMethod. Таким образом, вы получите поведение транзакции и изящную обработку исключений.

Вы можете поиграться с обычными механизмами OO, чтобы разобраться с вопросом о том, чтобы сделать AnotherChildClass взаимозаменяемыми для BaseClass, или использовать интерфейс и т. Д. И т. Д.

public class AnotherChildClass
{
    private readonly BaseClass _bling;

    public AnotherChildClass(BaseClass bling)
    {
        _bling = bling;
    }

    public void AnotherMethod()
    {
        try
        {
            _bling.SomeMethod();
        }
        catch (Exception)
        {
            //Do nothing...
        }
    }
}

Например, немного, но вы получите изображение:

public class AnotherChildClass : BaseClass
{
    private readonly BaseClass _bling;

    public AnotherChildClass(BaseClass bling)
    {
        _bling = bling;
    }

    public override void SomeMethod()
    {
        _bling.SomeMethod();
    }

    public void AnotherMethod()
    {
        try
        {
            _bling.SomeMethod();
        }
        catch (Exception)
        {
            //Do nothing...
        }
    }
}

Обновление

Я предполагаю, что из ваших последних исследований случаи, когда вы использовали 'new', не работают, поскольку вы сейчас блокируете сгенерированный прокси-сервер контейнера IoC от переопределения SomeMethod и, следовательно, внедрения кода. Попробуйте создать производный класс вашего класса Child и переопределить метод new SomeMethod. Это показывает, как прокси заблокирован.

    private class BaseClass
    {
        public virtual void SomeMethod(){}
    }

    private class ChildClass : BaseClass
    {
        public new void SomeMethod() //<- Declaring new method will block proxy
        {
            base.SomeMethod();
        }
    }

    private class ChildClassIocProxy : ChildClass
    {
        public override void SomeMethod() //<-- Not possible!
        {
            //Injected - before Tx
            base.SomeMethod();
            //Injected - after Tx
        }
    }
0 голосов
/ 27 июля 2011

На вашем месте я бы попробовал изменить ваш дизайн. Вызов base.SomeMethod () в AnotherMethod (), когда он был переопределен в классе AnotherMethod, действительно пахнет.

Не можете ли вы выделить в защищенном методе соответствующую часть BaseClass.SomeMethod (), поместить свой атрибут в этот новый метод и вызвать его в BaseClass.SomeMethod () и AnotherMethod (), предполагая, что ChildClass.SomeMethod () все равно будет вызывать метод, который он переопределяет?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...