是否可以从堆栈跟踪访问调用方对象并设置其属性

本文关键字:对象 设置 属性 方对象 堆栈 跟踪 调用 访问 是否 | 更新日期: 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"属性的东西调用了…