简体   繁体   English

如何使基类方法的属性适用于继承的类?

[英]How do I make attributes on a base class method apply in the inherited class?

Note: This question has been updated with new info. 注意:此问题已使用新信息进行更新。 Please see the bottom half of this text. 请参阅本文的下半部分。 (The original quesiton is left here for context.) (最初的问题留在这里是为了上下文。)


Is there any way I can define my attribute so that if it's defined on a method that is overidden, the attribute is still applied? 有没有什么方法可以定义我的属性,以便如果它被覆盖的方法定义,该属性仍然应用?

The reason I ask is that I have an attribute which injects some behavior into the method, but the behavior is not applied when the method is called as in any of the cases in the child class, and I would like it to be. 我问的原因是我有一个属性,它会在方法中注入一些行为,但是在子类的任何一种情况下调用方法时都不会应用这种行为,我希望它是这样。

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

The attribute is defined like so: 该属性定义如下:

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

The current code for finding methods with the attribute is the following: 使用该属性查找方法的当前代码如下:

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

I didn't write that part, and I can't say I am 100% of what every part of it does... But we can assume, for now, that I am in control of all the involved code. 我没有写那部分,我不能说我是它的每一部分的100%......但是我们现在可以假设我控制了所有涉及的代码。 (It's an opensource project, so I can at least create my own version, and submit a patch to the project owners...) (这是一个开源项目,所以我至少可以创建自己的版本,并向项目所有者提交补丁......)

I have a couple of other cases too - basically I want to have this behavior injected whenever I call the method on the base class, no matter which way I got there - but if I solve this one I might get ideas on how to get the others working. 我还有其他几个案例 - 基本上我想在基类上调用方法的时候注入这个行为,无论我在哪里 - 但如果我解决了这个问题,我可能会得到关于如何获得其他人工作。 If not, I'll get back with them. 如果没有,我会和他们一起回来。


UPDATE: 更新:

OK, so I sat down with the Castle.Transactions project and created some very simple tests to see what works and what doesn't. 好的,所以我坐下来使用Castle.Transactions项目,并创建了一些非常简单的测试,以查看哪些有效,哪些无效。 It turns out that my original assumptions on what works and what doesn't were somewhat off. 事实证明,我对什么起作用和什么不起作用的原始假设有点偏离。

What I did: 我做了什么:
I created a test class which has one method, decorated with the attribute, and which calls an Assert method that verifies that the behavior was injected correctly (ie that there is a transaction). 我创建了一个测试类,它有一个方法,用属性修饰,并调用一个Assert方法来验证行为是否正确注入(即有一个事务)。 I then created a couple of classes which inherit this test class, to see in which cases everything works as I expect it to. 然后我创建了几个继承这个测试类的类,看看在哪些情况下一切都按照我的预期运行。

What I found: 我找到了什么:
By calling the test method directly on the test class and from various methods on the child classes, I discovered the following about what works and what doesn't: 通过直接在测试类和子类的各种方法上调用测试方法,我发现了以下有关哪些有效,哪些有效:

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

In all cases except the one marked with * , base.SomeMethod() was called from the method applied in the test. 在除了标有*那个以外的所有情况下,从测试中应用的方法调用base.SomeMethod() In the first case the same method was called but directly from the test, since no child class is involved. 在第一种情况下,调用相同的方法,但直接从测试中调用,因为不涉及子类。 In the second case (of the ones marked * ), the overriding method was called, ie this.SomeMethod() , so that's really equivalent of the last case. 在第二种情况下(标记为*的那些),调用了重写方法,即this.SomeMethod() ,因此它实际上等同于最后一种情况。 I'm not using any redundant qualifiers, so in that method the call is simply SomeMethod() . 我没有使用任何冗余限定符,因此在该方法中,调用只是SomeMethod()

What I want: 我想要的是:
It's the case marked "headache" that I really want to solve; 这是我真正想要解决的标志性“头痛”的案例; how to inject behavior into the base class even though I'm calling it from my child class. 如何将行为注入基类,即使我从我的子类调用它。

The reason I need that specific case to work is that I'm using this pattern in a repository, where the base class defines a Save(T entity) method decorated with the Transaction attribute. 我需要特定情况才能工作的原因是我在存储库中使用此模式,其中基类定义了使用Transaction属性修饰的Save(T entity)方法。 Currently, I have to override this method just to get the transaction orchestration, which makes it impossible to change the return type; 目前,我必须重写此方法只是为了获取事务编排,这使得无法更改返回类型; on the base class it's void , but on my implementation I'd like to make it Error<T> instead. 在基类上它是void ,但在我的实现上我想把它改为Error<T> This is impossible when overriding, and since I can't solve the problem by naming the method differently, I'm at a loss. 当覆盖时这是不可能的,因为我不能通过不同地命名方法来解决问题,所以我很茫然。

If I were you I'd try and change your design. 如果我是你,我会尝试改变你的设计。 Calling base.SomeMethod() in AnotherMethod() when it has been overridden in AnotherMethod's class really smells. 在AnotherMethod的类中重写它时,在AnotherMethod()中调用base.SomeMethod()真的很有气味。

Can't you factor out in a protected method the relevant part of BaseClass.SomeMethod(), place your attribute on this new method and call it in BaseClass.SomeMethod() and AnotherMethod(), assuming ChildClass.SomeMethod() would still call the method it overrides? 你不能在受保护的方法中分解BaseClass.SomeMethod()的相关部分,将你的属性放在这个新方法上并在BaseClass.SomeMethod()和AnotherMethod()中调用它,假设ChildClass.SomeMethod()仍然会调用它重写的方法?

Can't go over it. 不能过去。 Can't go under it. 不能去吧。 Gotta go around it. 要绕过它。

Considering your circumstances as I understand them: 考虑到我了解他们的情况:

  • Can't apply the existing attribute to inject commit/rollback: It would never roll back because you're catching exceptions yourself in AnotherMethod() . 无法应用现有属性来注入提交/回滚:它永远不会回滚,因为您在AnotherMethod()自己捕获异常。
  • You need the commit/rollback injection in AnotherMethod() . 您需要AnotherMethod() 提交/回滚注入

I suspect that TransactionAttribute is wrapping the method's body in a try-catch block, transforming this (pseudocode): 我怀疑TransactionAttribute将方法的主体包装在try-catch块中,转换为此(伪代码):

public void SomeMethod() {
    DoStuff();
}

Into something like this (pseudocode, and very simplified): 进入类似的东西(伪代码,非常简化):

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

With that in mind, you may be able to apply TransactionAttribute to AnotherMethod() and re-throw the exceptions you catch: 考虑到这一点,您可以将TransactionAttribute应用于AnotherMethod()并重新抛出您捕获的异常:

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

If that is not feasible -- such as if you only want part of the behavior that TransactionAttribute injects -- then you will likely have to make a new TransactionAttribute that injects the behavior you want it to inject. 如果这不可行 - 例如,如果您只想要TransactionAttribute注入的部分行为 - 那么您可能必须创建一个新的TransactionAttribute ,它注入您希望它注入的行为。 One possibility might be that it looks for try-catch blocks and places commits and rollbacks in the appropriate places, but that could be trickier than the current version. 一种可能性是它寻找try-catch块并在适当的位置放置提交和回滚,但这可能比当前版本更棘手。

Shot in the dark here, but... 在黑暗中拍摄,但......

I am assuming that the transaction behavior is injected by an IoC container, and that it does this by making a proxy when you resolve a ChildClass . 我假设事务行为由IoC容器注入,并且它通过在解析ChildClass时创建代理ChildClass Therefore the transaction code runs before\\after ChildClass.SomeMethod via the proxy. 因此,事务代码通过代理在ChildClass.SomeMethod之后运行。 I'm guessing that the behavior you're seeing is that there is no code injection on BaseClass.SomeMethod , so calling it from ChildClass.AnotherMethod does not involve any proxy code injection, it just goes straight through. 我猜你所看到的行为是BaseClass.SomeMethod上没有代码注入,所以从ChildClass.AnotherMethod调用它不涉及任何代理代码注入,它只是直接通过。

If this is the case, you could use a composition pattern and injection of a BaseClass to solve the problem. 如果是这种情况,您可以使用合成模式并注入BaseClass来解决问题。

If you resolved the following class via your container, it would inject a proxied BaseClass which had the appropriate before\\after transaction code for BaseClass.SomeMethod method. 如果您通过容器解析了下面的类,它将注入一个代理的BaseClass ,该BaseClass具有适用于BaseClass.SomeMethod方法的事务代码之前\\后的代码。 You would therefore get your transaction behavior, plus your graceful exception handling. 因此,您将获得您的交易行为,以及优雅的异常处理。

You can play around with the usual OO mechanisms to sort out the issue of making AnotherChildClass interchangeable for a BaseClass , or use an interface, etc, etc. 您可以使用常用的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...
        }
    }
}

For example, a bit urgh, but you get the picture: 例如,有点呃,但你得到的图片:

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...
        }
    }
}

Update 更新

I'm guessing that from your latest investigations the cases where you have used 'new' are not working as you are now blocking the generated IoC container proxy from overriding SomeMethod and therefore injecting the code. 我猜你在最近的调查中使用'new'的情况不起作用,因为你现在阻止生成的IoC容器代理覆盖SomeMethod并因此注入代码。 Try creating a derived class of your Child class, and try overriding the new SomeMethod method. 尝试创建Child类的派生类,并尝试重写new SomeMethod方法。 This illustrates how the proxy is blocked. 这说明了代理是如何被阻止的。

    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
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM