【问题标题】:C# method observableC# 方法可观察
【发布时间】:2020-08-06 17:03:40
【问题描述】:

我有一个方法,每当发生事情时都会调用它。它可以被调用任意次数。然后方法返回一个任务。基本上,我想在调用此方法时设置一个“可观察的”。我无法控制被调用的方法。被调用的方法是虚拟的,所以我可以覆盖它以查看它是否被调用。但我宁愿设置一个“可观察的”。所以我有类似的东西

class Foo
{
  void Bar()
}

同样,我无法控制 Foo,但我有它的一个实例。

var instanceofFoo = new Foo();

我想设置一个“可观察”,只要调用 instanceOfFoo.Bar 就会触发。

我想要一个“可观察”的原因是因为我不想为可能的下一次调用再次设置“观察者”。我正在使用 C# 进行编码,我查看了 IObservable<T>IObserver<T>,但它明确指出“提供者”必须实现 IObservable<T>。由于我无法控制提供者(在本例中为 Foo 的实例),因此我无法使用它。我查看了 Rx.NET,但无法从示例中收集到适合这种情况的东西。更复杂的是,假设 Foo 的实例是另一个类的依赖项,例如

class FooService
{
    public bool wasCalled { get; set; }
    public FooService(Foo f)
    {
       // TODO: Setup code to trigger when f.Bar() is called
    }
}

此设置需要能够彻底拆除,以便没有悬空的“订阅者”。

所以流程应该是这样的

  1. var foo = new Foo();
  2. var fooService = new FooService(foo);
  3. foo.Bar();
  4. Assert.IsTrue(fooService.wasCalled);

想法?

【问题讨论】:

  • 您一直在引号中使用“可观察”一词,就好像您的意思是要赋予您将要解释的某些含义——但是您却永远没有时间解释它。所以我想知道你的意思,具体来说。你能努力找到正确的术语吗?或者不使用未定义的新词,用简单的术语解释?我猜你想要某种event 或者你想要在调用Foo 之前或之后执行代码。
  • 如果您可以覆盖该方法,那么一种选择可能是覆盖该方法,调用基本方法,然后触发您的通知事件。
  • 您可能正在寻找“拦截” - 如果您将 Unity 用于 DI 并且您尝试查看的方法是某个接口(或至少是虚拟接口)的一部分,那么它应该是可行的。如果你无法控制Foo创建,我猜你会寻找某种程序集重写。
  • 将其包装在您自己控制的类型中,并且只能通过您的类型访问它。
  • @JohnWu 我说“可观察”就像 RXjs 定义可观察对象的方式一样。据我所知,C# 没有等效的概念。我不想要一个用于测试目的的事件。为了测试,我想模拟依赖关系,所以当我对 Bar() 进行模拟调用时,我可以观察 FooService 中的后果。

标签: c# observable


【解决方案1】:

我不确定您为什么专注于“可观察对象”,但 C# 已经有一个用于订阅者/发布者类型交互的系统,即 events

你可以使用代理模式结合继承来实现你想要的:

class FooEventProxy : Foo
{
    public event EventHandler BarCall;

    /*
     *   Retype all constructors of Foo here and delegate them to base.
     */

    public override void Bar()
    {
        OnBarCall(new EventHandler());
        base.Bar();
    }

    protected virtual void OnBarCall(EventHandler e)
    {
        EventHandler event = BarCall;

        if (event != null)
        {
            event(this, e);
        }
    }
}

这是假设您控制Foo 的创建,并且可以强制它创建的所有地方改为使用FooEventProxy

如果你不能,那么你只是发现了将所有创建逻辑保留在工厂中的原因,情况有点复杂。如果您可以访问 Foo 实例化的所有参数,您可能只需将它们复制到 FooEventProxy 的新实例。如果你不能,那么我们就陷入了一个难题:你想用FooEventProxy 替换Foo 类型的参数,但是你不能确保参数的内部状态会正确地转移到新实例中.您不能只将虚拟方法调用路由到包装的实例,因为非虚拟方法仍然可以调用并且无法正常工作 - 违反了 Liskov 原则。因此,您需要使用大量反射巫术将Foo 的内部状态完全复制到FooEventProxy 中,或者,您运气不好,您刚刚找到了依赖接口/摘要而不是具体的原因类。

我不知道如何将所有基类数据从现有实例复制到派生类型的新实例中,所以如果你真的认为这是你想要的,请专门问另一个问题为此。

【讨论】:

  • 我想避免事件,因为我对测试感兴趣。为了测试,我想模拟依赖关系,所以当我对 Bar() 进行模拟调用时,我可以观察 FooService 中的后果。(一个方法被称为某些属性被更改。等等)
【解决方案2】:

听起来这只是用于单元测试。我可以根据您的限制为您提供两个答案。

如果您能够修改代码,那么最好的(也是大多数SOLID)方法是依赖接口而不是具体的类,必要时进行包装。然后在您的单元测试中,您可以替换一个调用原始方法但也执行其他操作(例如记录它)的方法调用。

interface IFoo
{
    void Bar();
}

class Foo : IFoo
{
    public void Bar() { }
}

然后在你的测试代码中:

class FooShim : IFoo
{
    protected readonly Foo _foo;
    protected readonly Action _action;

    public FooShim(Foo foo, Action action) 
    {
         _foo = foo;
         _action = action;
    }

    public Bar()
    {
        action();
        _foo.Bar();
    }
}


//Arrange
bool wasCalled = false;
var foo = new FooShim(new Foo(), () => wasCalled = true);
var fooService = new FooService(foo);

//Act
fooService.DoSomethingThatCallsFoo();

//Assert
Assert.IsTrue(wasCalled);

如果你不能修改代码,你可以做类似的事情,但你需要一个特殊的框架,比如TypeMock。 TypeMock 利用 .NET CLR 内部机制,允许您拦截和替换方法调用。

Isolate.WhenCalled( () => foo.Bar()).WillBeReplacedWith( () => wasCalled = true );

【讨论】:

  • 谢谢。这个解决方案的问题是它对我来说似乎倒退了。基本上它是在 fooService 中调用一个方法,然后检查 FooShim 中的方法是否被调用。我一直在寻找的是调用依赖项上的方法,并且资产在 FooService 上的某些内容发生了变化。所以说 wascall 是 FooService 上的一个属性,我想在调用依赖项上的方法时断言 wasCalled 为真。
  • 你的例子和你的解释我不清楚。如果您只想断言某些属性已更改,我认为不需要拦截——您的测试代码可以在调用返回后检查该属性。也许你可以在你的问题中添加一些额外的代码来澄清。
  • 我有一个类 FooService,它依赖于 Foo 或 IFoo。无论哪种方式,Foo 都有一个 Bar 方法。当我在 Foo 或 IFoo 上调用 Bar 方法时,我希望能够断言 FooService 上的属性已更改。所以流程类似于: 1) var foo = new Foo(); 2) var fooService = 新的 FooService(foo)。 3) 调用 Foo 方法 foo.Bar() 4) Assert(fooService.wasCalled == true).
  • 评论里刚才写的代码有什么问题?您在哪里需要可观察或类似可观察的行为?
  • 我缺少的是属性 wasCalled 和对方法 Bar() 的调用之间的连接吗?
猜你喜欢
  • 1970-01-01
  • 2010-09-15
  • 2018-01-13
  • 1970-01-01
  • 1970-01-01
  • 2017-12-02
  • 1970-01-01
  • 2011-06-17
  • 1970-01-01
相关资源
最近更新 更多