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

时间:2011-07-27 15:45:06

标签: c# inheritance attributes

注意:此问题已使用新信息进行了更新。请参阅本文的下半部分。 (最初的问题留在这里是为了上下文。)


有没有什么方法可以定义我的属性,这样如果它是在一个被覆盖的方法上定义的,那么该属性仍然被应用?

我问的原因是我有一个属性会在方法中注入一些行为,但是在子类中的任何一种情况下调用方法时都不会应用该行为,我希望它是。

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 :(得分:0)

如果我是你,我会尝试改变你的设计。在AnotherMethod的类中重写它时,在AnotherMethod()中调用base.SomeMethod()真的很有气味。

你不能在受保护的方法中分解BaseClass.SomeMethod()的相关部分,将你的属性放在这个新方法上并在BaseClass.SomeMethod()和AnotherMethod()中调用它,假设是ChildClass.SomeMethod()仍然会调用它覆盖的方法吗?

答案 1 :(得分:0)

不能过去。不能去吧。要绕过它。

考虑到我理解他们的情况:

  • 无法应用现有属性来注入提交/回滚:它永远不会回滚,因为您自己在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块并在适当的位置放置提交和回滚,但这可能比当前版本更棘手。

答案 2 :(得分:0)

在黑暗中拍摄,但是......

我假设事务行为是由IoC容器注入的,并且它通过在解析ChildClass时创建代理来实现。因此,事务代码在ChildClass.SomeMethod之后通过代理运行。我猜你所看到的行为是BaseClass.SomeMethod上没有代码注入,所以从ChildClass.AnotherMethod调用它不涉及任何代理代码注入,它只是直接通过。

如果是这种情况,您可以使用合成模式并注入BaseClass来解决问题。

如果您通过容器解析了以下类,它将注入一个代理BaseClass,它具有适用于BaseClass.SomeMethod方法的事务代码之前\后的AnotherChildClass。因此,您将获得交易行为,以及优雅的异常处理。

您可以使用通常的OO机制来解决使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

<强>更新

我猜你从最近的调查中看到你使用'new'的情况不起作用,因为你现在阻止生成的IoC容器代理覆盖SomeMethod并因此注入代码。尝试创建Child类的派生类,并尝试覆盖 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 } } SomeMethod方法。这说明了代理是如何被阻止的。

{{1}}