是否可以从堆栈跟踪访问调用方对象并设置其属性
本文关键字:对象 设置 属性 方对象 堆栈 跟踪 调用 访问 是否 | 更新日期: 2023-09-27 18:01:12
嗨,我可能有一个设计缺陷,我需要用扩展方法来解决它。假设我有一个类,它有一个StringCollection属性。示例代码
public class MyProblematicClass
{
public IDbAccess Db{get;set;}
public StringCollection Errors{get;set;}
public MyProblematicClass(IDbAcces db){ Db=db;}
public int SetItem(Item i)
{
var id = Db.Save(i);
this.Errors = Db.Erros;
return id;
}
}
我正在做的是,在我的单元测试类中,我模拟IDbAccess。此类根据属性验证对象。如果发生任何错误,它不会命中数据库,只会填充自己的Errors集合。对于单元测试,我使用另一个dbclass,它只运行验证例程,这是一个我无法得到错误的问题。让我给你举个例子来进一步理解(我知道设计有问题,但现在我想在不改变任何东西的情况下处理它(
public static class MyDbExtension
{
public static Save(Item i)
{
Validation v = new Validation();
var erros = v.ValidateObject(i);
//Here is problem i cannot pass it to MyProblematicClass
if ( errors.Count > 0 )
return -1;
else
return 1;
/* what I want to is :
var stackTrace = new StackTrace(); get stack trace
var object = stackTrace.GetFrame(1).GetMethod().GetObject() or sth like that. get object
object.GetProperties()[0].SetValue(object,errors,null); find property and set it.
*/
}
}
在我的单元测试中:
public class UnitTest
{
Mock<IDbAccess> _db ;
MyProblematicClass _mpc;
pubic Setup()
{
_db.Setup(x=>x.Save(It.IsAny<Item>).Returns(u =>MyDbExtension.Save(u));
_mpc = new MyProblematicClass(_db.Object);
}
public void SetItem_EmptyObject_Contains3Erros()
{
Item i = new Item();
_mpc.SetItem(i);
//At this point i cannot set _mpc.Errors
}
我想实现的是,在我的DbExtension类中,我可以访问调用者类并设置其Errors属性吗?我试过了,但还不太可能。如果有人有任何像样的解决方案,我将不胜感激,当然你可以评论设计问题。
编辑
我很欣赏Alex的回答,他刚才说忽略Save方法,只模拟Erros属性,这就可以了。这很有道理,但我想知道的是,有可能访问Stack Trace并操纵调用者方法对象的属性吗?
提前谢谢。
您需要设置_db的返回值。错误如下:
public class UnitTest
{
Mock<IDbAccess> _db ;
MyProblematicClass _mpc;
StringCollection errors;
pubic Setup()
{
_db.Setup(x=>x.Save(It.IsAny<Item>).Returns(u =>MyDbExtension.Save(u));
_db.Setup(x=>x.Errors).Returns(errors);
_mpc = new MyProblematicClass(_db.Object);
}
public void SetItem_EmptyObject_ContainsError()
{
errors.Add("Expected Error!");
Item i = new Item();
_mpc.SetItem(i);
Assert.AreEqual("Expected Error!", _mpc.Errors[0]);
}
}
我必须承认,我并没有真正遵循你的设计,为什么你要使用静态方法来保存?你可以很容易地拥有这条线:
_db.Setup(x=>x.Save(It.IsAny<Item>).Returns(-1);
然后独立测试IDbAccess.Save((。
在"extension"类中,save方法没有返回值,MyProbematicClass在分配错误之前不会检查返回值。
不一定完全理解这个问题,但您无法从普通程序访问堆栈上的参数。运行时元数据只涉及静态信息(方法、属性、常量等(。
我相信只有调试器(它被认为是自己的特殊野兽(才能在不更改程序/源代码的情况下做到这一点,这会带来严重的性能成本。附带说明一下,这里有一个链接解释了如何构建自己的托管调试器(.NET 4(:CLR托管调试器(mdbg(示例4.0
另一种解决方案是(自动或使用工具(插入代码,以添加一些跟踪调用,该调用可以捕获每个跟踪方法的参数列表。像PostSharp这样的工具可以做到这一点。这里有另一个链接:无创追踪&记录
您可以使用非托管调试API来访问调用堆栈,并获取调用堆栈上的前一个函数的对象。
问题是,堆栈可能不包含您期望的方法。在内联和尾部调用优化等情况下,调用堆栈不包含前一个调用的方法,这意味着您无法可靠地执行所需操作。
欲了解更多信息,请参阅Eric Lippert的回答。
这不使用调用堆栈,但可能会给您带来一些好处:
class CalledClass
{
public static void PokeCaller()
{
Program._this.Error = "Error!!!";
}
}
class Program
{
public string Error = null;
[ThreadStatic] public static Program _this;
public void Run()
{
_this = this;
CalledClass.PokeCaller();
Console.WriteLine(Error);
Console.ReadKey();
}
static void Main(string[] args)
{
Program p = new Program();
p.Run();
}
}
将错误设置为[ThreadStatic]可能是一种更直接的方法……或者该主题的其他变体。您也可以将其与堆栈跟踪检查结合起来,看看在设置之前是否真的被具有"Errors"属性的东西调用了…