注意:此问题已使用新信息进行了更新。请参阅本文的下半部分。 (最初的问题留在这里是为了上下文。)
有没有什么方法可以定义我的属性,这样如果它是在一个被覆盖的方法上定义的,那么该属性仍然被应用?
我问的原因是我有一个属性会在方法中注入一些行为,但是在子类中的任何一种情况下调用方法时都不会应用该行为,我希望它是。
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>
。当覆盖时,这是不可能的,因为我无法通过不同的方式命名方法来解决问题,所以我很茫然。
答案 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}}