C# наблюдаемый метод - PullRequest
       30

C# наблюдаемый метод

0 голосов
/ 06 августа 2020

У меня есть метод, который вызывается всякий раз, когда что-то происходит. Его можно вызывать сколько угодно раз. Затем метод возвращает Task. По сути, я хочу настроить «наблюдаемый» на тот момент, когда вызывается этот метод. Я не могу контролировать вызываемый метод. Вызываемый метод является виртуальным, поэтому я могу его переопределить и увидеть, что он вызывается. Но я бы предпочел установить «наблюдаемое». Итак, у меня есть что-то вроде

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

Идеи?

Ответы [ 2 ]

1 голос
/ 06 августа 2020

Я не уверен, почему вы сосредотачиваетесь на «наблюдаемых», но C# уже имеет систему для взаимодействия типа подписчик / издатель, а именно события .

Вы можете использовать шаблон прокси в сочетании с наследованием для достижения желаемого:

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.

Если вы не можете, тогда вы только что выяснили причину, по которой все ваши логики создания c хранятся на фабриках, и ситуация немного сложнее. Если вы можете получить доступ ко всем аргументам Foo, с которыми он был создан, вы, вероятно, можете просто скопировать их в новый экземпляр FooEventProxy. Если вы не можете, то мы находимся в загадке: вы хотите заменить аргумент типа Foo на FooEventProxy, но вы не можете гарантировать, что внутреннее состояние аргумента будет правильно передано в новый экземпляр. . Вы не можете просто направить вызовы виртуальных методов в обернутый экземпляр, поскольку невиртуальные методы все еще могут быть вызваны и не будут работать правильно - нарушение принципа Лискова. Таким образом, вам нужно либо скопировать внутреннее состояние Foo точно в FooEventProxy, используя массивное отражение вуду, либо, ну, вам не повезло, и вы только что выяснили причину зависимости от интерфейсов / абстрактов, а не конкретных классы.

Я не знаю, как скопировать все данные базового класса из существующего экземпляра в новый экземпляр производного типа, что было у меня в голове, поэтому, если вы действительно думаете, что это то, что вы хотите, задайте еще один вопрос специально для этого.

0 голосов
/ 06 августа 2020

Похоже, это просто для модульного тестирования. Я могу дать вам два ответа, в зависимости от ваших ограничений.

Если вы можете изменить код, то лучший (и самый 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 );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...