DynamicData icon indicating copy to clipboard operation
DynamicData copied to clipboard

[Feature]: Keep dedicated .Sort(), instead of deprecating

Open jbooth88 opened this issue 1 year ago • 7 comments

Describe the functionality desired 🐞

With the .Sort() method being depreciated there are a couple of rare cases where applying a sort immediately is desired because the properties used in the comparison do not exist downstream.

example:

    changeSet
            .Sort(complexSortComparer) //Sorts on input data type
            .DistinctValues(x => x.Name) //DistinctValues is transforming to Name data type 
            .Bind(out names); //List of names is sorted 

Changing the call chain to use SortAndBind() is not possible in this scenario.

I had a conversation on Slack about this topic: https://reactivex.slack.com/archives/C4LF8S19N/p1733511277945719

The steps the functionality will provide

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Considerations

SortAndBind

jbooth88 avatar Dec 06 '24 22:12 jbooth88

Additional scenarios we discussed...

using var subscription = changes
    .Sort()
    .Transform() // Does not preserve properties needed for sorting
    .Bind()
    .Subscribe();
var sharedChanges = changes
    .Sort()
    .Publish();
    
using var subscription1 = sharedChanges
    .Bind()
    .Subscribe();
    
using var subscription2 = sharedChanges
    .Bind()
    .Subscribe();
    
using var connection = sharedChanges
    .Connect();

The second scenario is pretty compelling to me, as a potential performance improvement for not duplicating sort work. However, I also proposed in the thread that the performance of .Sort() is so poor that one instance of .Sort() might actually NOT be more efficient thant two instances of .SortAndBind(). Now, I'm curious to benchmark it.

JakenVeina avatar Dec 07 '24 03:12 JakenVeina

I think I came across another scenario where there's a use case for sorting before binding. If the output collection is a base type, but the observable stream coming in is a derived type.

If there's an elegant work around I'm not seeing, please let me know :)

protected void example(ISourceCache<DerivedType, int> cache)
{
    cache
        .Connect()
        .Sort(derivedTypeSort)
        .Transform(x => (BseType)x)
        .ObserveOn(uiScheduler)
        .Bind(out collection)
        .Subscribe()
        .DisposeWith(this);
}
private ReadOnlyObservableCollection<BaseType> collection;
public IObservable<SortExpressionComparer<DerivedType>> DerivedTypeSort => //Sort impl

jbooth88 avatar Feb 03 '25 16:02 jbooth88

I am a bit late to this show. I meant to respond and promptly forgot.

There is a potentially a solution, and that is to take advantage of the list operators which respect sorted data sets (mostly). Try changing the cache to a list by removing the keys before Sort like this:

    changeSet
            .RemoveKey()  // This converts the operator into a list operator which is index based.
            .Sort(complexSortComparer) //Sorts on input data type
            .DistinctValues(x => x.Name) //DistinctValues is transforming to Name data type 
            .Bind(out names); //List of names is sorted 

RolandPheasant avatar Jun 04 '25 05:06 RolandPheasant

@RolandPheasant how performant is that solution? Seems to me like this SHOULD be decent as long as a user is smart about what comes AFTER the sort?

giard-alexandre avatar Jun 04 '25 14:06 giard-alexandre

The performance should be similar to the original cache sort.

RolandPheasant avatar Jun 05 '25 19:06 RolandPheasant

I will switch our organization's apps to Roland's suggestion over time. I tested with one project and it seems to work just fine.

I had thought there were some issues with SourceList that didn't occur when using a SourceCache, but maybe those problems were fixed in PR #1009. My requirements for .Sort() before .Bind() are so simple I think it doesn't matter and this will work.

@RolandPheasant the second scenario I posted where the cache is a derived type but the output collection is a base type, is there a work around for that? Or is there a covariance issue with the Bind() or SortAndBind() methods? (See my comment on Feb 3)

jbooth88 avatar Jun 05 '25 20:06 jbooth88

there were some issues with SourceList that didn't occur when using a SourceCache

SourceList is fine, it's just limited by a difficulty to ensure good performance. The existing operators are sound and should work well. The only "issue" is that I drew a line in the sand and limited source list to certain operators only. In practise however, I have only ever had a few scenarios where I could not use a cache key or manufacture a quasi one - hence the limited scope.

The advantage however with using the list is that it is naturally suited to binding as a bound list is indexed based and for the most part respected the order.

@jbooth88

    cache
        .Connect()
        .RemoveKey()
        .Sort(derivedTypeSort)
        .Transform(x => (BseType)x)
        .ObserveOn(uiScheduler)
        .Bind(out collection)
        .Subscribe()
        .DisposeWith(this);

Should working fine. Try it out an let me know.

Of the basic list operators, it is only Filter() which does not preserve the order. It's probably possible to fix this, but would require a fair bit of effort.

RolandPheasant avatar Jun 07 '25 10:06 RolandPheasant