-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle IStore subscription like redux.js #58
Conversation
Wait, I don't get it: Wasn't the intention to use a standard event ( This PR seems like it's neither – no event, and a
Note that the above quote was written under the misunderstanding that the 2.0.0 |
To be clear: Personally, I would be satisfied with Rx only, but I think we should do either a standard event (which calls delegates on subscription) with Rx as extension methods or similar, or Rx only. |
I think there are reasons that redux behaves the way it does for predictability and extensions and such. It has specific tests for ensuring that subscribers have their callbacks caled at the right time and not during certain execution cycles. You can check out the redux.js source code and unit tests to see it. It should be pretty simple to have a decorator or store enhancer that exposes the events if we want to do that but since they would use the underlying implementation of subscribe and getstste, then the behavior would still adhere to the current redux spec. |
@lilasquared Could you be specific as to what would be unsatisfactory with events or IObservable.Subscribe, that is fixed when using the Subscribe method from this PR? |
@cmeeren nothing that's fixed with this PR because of the todo comment. I think the test from redux.js though deals with subscribe and unsubscribe. The tests are here I can't link to the lines cause I'm on mobile but a few of the subscribe and unsubscribes don't pass unless you manage the listeners internally like redux.js does. Whether it's satisfactory or not, I don't know enough about all of the extensions to say. I am more just bringing it up because of the current redux.js spec. |
{ | ||
return Observable.Create<T>(observer => | ||
{ | ||
return store.Subscribe(() => observer.OnNext(store.GetState())).Dispose; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pulled this down and it won't build here - @GuillaumeSalles does it build for you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh... I forgot to save a file before commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder why the build didn't fail for the CI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. It's weird. I'll a take a look later.
When I was working on this PR, it felt weird to have an event without parameter. But honestly, I don't have any preferences between However, IMHO IObservable.Subscribe is not correct (even if IObservable is an interface in System) for those reasons :
|
@lilasquared So what we need to do is to decide how we want Redux.NET to work, which may not be exactly the same as redux.js in all cases. This may be purely design decisions on our part, or it may be differences due to the fact that C# is a very different language than JS with different syntax, features, and conventions. I've skimmed through the redux.js tests you linked to, and I don't immediately see any tests concerning subscribe/unsubscribe that are 1) represents a good idea in .NET, and 2) not easily attainable using either plain events or Rx. In fact, several of the tests we don't need because we get them for free with events/Rx, such as There are a few features that I'm not sure really is a good idea (please let me know if/how I'm wrong). When reading the below, keep in mind that like all good libraries, we should strive to push our users towards sensible design decisions. Specifically, it concerns the In a .NET sense, one is never guaranteed the order in which standard event handlers will get called. Thus, unsubscribing handler A from handler B would be asking for trouble if you really need to be sure that handler A also runs for the same dispatch where you unsubscribe it from B. AFAIK these "asking for trouble, don't do it" semantics are the ones .NET devs are used to, and it seems to me that having this as a feature would be just to cater to some unwise design decisions on the part of our users. (Of course, please let me know if you have a solid use case for this that can't be catered to in a more robust manner.) The point is, .NET has well-defined semantics for subscribing and unsubscribing event handlers (and IObservable listeners for those familiar with Rx). This is not directly transferable to/from JS. Again, I don't think it should be a goal in and of itself to make Redux.NET work 100% the same as redux.js. I think we should strive first and foremost to make the redux architecture easy to use in .NET. (Though of course, we shouldn't create significant differences without good reason.) |
I certainly do at the moment, though this may be due to limitations of my knowledge. @lilasquared may have valid rebuttals to my previous comment.
When using Rx, the Subscribe method has plenty of overloads. I hadn't really thought about it, but if not using Rx means you will have to implement IObservable for all subscribers, then yes, that won't fly. (Unless we decide to force users to depend on Rx - then it's just as easy as adding an event handler.) But yes, I agree that in the "base" store a |
@cmeeren It would take me some time to get a good example of something that might not work with events. In the meantime I looked at the store api docs on redux.js:
if we decide to go with events, Do you see any issues with supporting nested dispatch as they do here? |
There is no problem calling
This works out of the box, because multicast delegates (e.g. event handlers) are immutable, so any unsubscription will not affect the current invocation of the delegates. I wrote a gist that tests this and passes. I also wrote an SO question before I found out about the immutability of multicast delegates. Anyhow, all the sentiments I can find online about the invocation order of .NET event handlers indicates that if you rely on a specific order, then it is an indication of a (possibly serious) design problem, and I would agree. It will also indicate a design problem even if we use a custom
This does not work out of the box, as this gist shows. Though again, this seems to assume some ordering of invoked handlers, which as mentioned seems to indicate design problems in the .NET world. If we really want to support it, I'm sure there's some way we can do that with a normal event. I think the logic would lie in the |
Thanks @cmeeren and @lilasquared for your feedback/analysis. Store now use @lilasquared Regarding nested dispatch, every cases are handle in this PR. If I update your gist like this, everything work. public class EventSource
{
private int i;
public int GetState()
{
return i;
}
public event Action Event;
public void TriggerEvent()
{
i++;
this.Event?.Invoke();
}
}
public class EventSourceTests
{
[Test]
public void When_InvokingFromHandler_Should_InvokeAllWithLatestState()
{
// Arrange
var listener1InvokedState = 0;
var listener2InvokedState = 0;
var listener3InvokedState = 0;
var sut = new EventSource();
Action listener1 = null;
Action listener2 = null;
Action listener3 = null;
listener1 = () => listener1InvokedState = sut.GetState();
listener2 = () =>
{
listener2InvokedState = sut.GetState();
if (sut.GetState() == 1) sut.TriggerEvent();
};
listener3 = () => listener3InvokedState = sut.GetState();
sut.Event += listener1;
sut.Event += listener2;
sut.Event += listener3;
// Act
sut.TriggerEvent();
// Assert
Assert.That(listener1InvokedState, Is.EqualTo(2));
Assert.That(listener2InvokedState, Is.EqualTo(2));
Assert.That(listener3InvokedState, Is.EqualTo(2));
}
} I ignore possible multi-thread issues right now to focus on the shape of the API and the internal behavior. This behavior will be removed in another PR. Help is welcome :) I think that every concerns is resolved so I merge this PR. Feel free to comment if I missed something. |
Yes, of course, that would work. Then AFAICS The PR looks good to me. |
What behavior? |
#57 should be close before this one
Thanks again @lilasquared and @cmeeren to have insisted. The API is now much cleaner.