简体   繁体   English

C# 方法可观察

[英]C# method observable

I have a method that is called whenever something happens.我有一个方法,每当发生事情时都会调用它。 It can be called any number of times.它可以被调用任意次数。 Then method returns a Task.然后方法返回一个任务。 Basically I want to setup an "obserable" for when ever this method is called.基本上,我想在调用此方法时设置一个“可观察的”。 I have no control over the method being called.我无法控制被调用的方法。 The method that is called is virtual so I can override it an see that it gets called.被调用的方法是虚拟的,所以我可以覆盖它以查看它是否被调用。 But I would rather setup an "observable".但我宁愿设置一个“可观察的”。 So I have something like所以我有类似的东西

class Foo
{
  void Bar()
}

Again I have no control over Foo but I have an instance of it.同样,我无法控制 Foo 但我有它的一个实例。

var instanceofFoo = new Foo();

I want to setup an "observable" that will be triggered anytime that instanceOfFoo.Bar is called.我想设置一个“可观察的”,只要调用instanceOfFoo.Bar就会触发。

The reason that I want an "observable" is because I don't want to have to setup the "observer" again for possibly the next call.我想要一个“可观察”的原因是因为我不想为可能的下一次调用再次设置“观察者”。 I am coding in C# and I looked at IObservable<T> and IObserver<T> but there it specifically indicated that the "provider" had to implement IObservable<T> .我在 C# 中编码,我查看了IObservable<T>IObserver<T>但它明确指出“提供者”必须实现IObservable<T> Since I don't have control over the provider (in this case the instance of Foo) I cannot use this.由于我无法控制提供者(在本例中为 Foo 的实例),因此我无法使用它。 I looked at Rx.NET but was unable to glean from the examples something that fit this situation.我查看了 Rx.NET,但无法从示例中收集到适合这种情况的内容。 To further complicate things assume that the instance of Foo is a dependency of another class like更复杂的事情假设 Foo 的实例是另一个 class 的依赖项,例如

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

This setup would need to be able to be cleanly torn down so that there are no dangling "subscribers".此设置需要能够被彻底拆除,以便没有悬空的“订阅者”。

So the flow would be something like this所以流程会是这样的

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

Ideas?想法?

I'm not sure why you focus on "observables", but C# has a system for subscriber/publisher type of interaction already, namely events .我不确定您为什么专注于“可观察对象”,但是 C# 已经有一个用于订阅者/发布者类型交互的系统,即events

You can use the Proxy pattern combined with inheritance to achieve what you want:您可以使用代理模式结合 inheritance 来实现您想要的:

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);
        }
    }
}

This is assuming that you control the creation of Foo and can just force all places it's created to use FooEventProxy instead.这是假设您控制Foo的创建,并且可以强制所有创建它的地方使用FooEventProxy代替。

If you can't then you just found out the reason for keeping all your creation logic in factories and the situation is a bit more complicated.如果你不能,那么你只是找到了将所有创建逻辑保留在工厂中的原因,情况有点复杂。 If you can access all the arguments of Foo that it was instantiated with you can probably just copy them over to the new instance of FooEventProxy .如果您可以访问Foo实例化的所有 arguments ,您可能只需将它们复制到FooEventProxy的新实例。 If you can't then we're in a conundrum: you want to replace an argument of type Foo with FooEventProxy , but you can't ensure that the internal state of the argument will be correctly transferred into the new instance.如果你不能,那么我们就陷入了一个难题:你想用FooEventProxy替换类型为Foo的参数,但你不能确保参数的内部 state 将正确地传输到新实例中。 You can't just route the virtual method calls to the wrapped instance, since the non-virtual methods can still be called and will not work correctly - violation of the Liskov principle.您不能只将虚拟方法调用路由到包装实例,因为仍然可以调用非虚拟方法并且无法正常工作 - 违反了 Liskov 原则。 So you need to either copy the internal state of Foo exactly into FooEventProxy using massive reflection voodoo or, well, you're out of luck and you've just found out the reason for depending on interfaces/abstracts and not concrete classes.因此,您需要使用大量反射巫术将Foo的内部 state 完全复制到FooEventProxy中,或者,您运气不好,您刚刚发现了依赖接口/抽象而不是具体类的原因。

I don't know how to copy over all base class data from an existing instance to a new instance of a derived type off the top of my head, so if you really think that's what you want, please ask another question specifically for that.我不知道如何将所有基本 class 数据从现有实例复制到我脑海中的派生类型的新实例,所以如果你真的认为这是你想要的,请专门为此提出另一个问题。

Sounds like this is just for unit testing.听起来这只是用于单元测试。 I can provide you with two answers, depending on your constraints.我可以根据您的限制为您提供两个答案。

If you are able to modify the code, then the best (and most SOLID ) approach is to depend on an interface instead of a concrete class, wrapping if necessary.如果您能够修改代码,那么最好的(也是最可靠的)方法是依赖接口而不是具体的 class,必要时进行包装。 Then in your unit tests you can substitute a method call that calls the original method but also does something else (eg logs it).然后在您的单元测试中,您可以替换一个调用原始方法但也执行其他操作(例如记录它)的方法调用。

interface IFoo
{
    void Bar();
}

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

Then in your test code:然后在您的测试代码中:

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);

If you are not able to modify the code, you can do something similar, but you will need a special framework like TypeMock .如果你不能修改代码,你可以做类似的事情,但你需要一个特殊的框架,比如TypeMock TypeMock takes advantage of .NET CLR internals to allow you to intercept and replace method calls. TypeMock 利用 .NET CLR 内部结构来拦截和替换方法调用。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM