Skip to content
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

BindingSource.Sort fails with custom IBindingListView in .NET 9 (worked in .NET 8) #13061

Open
JimWilcox3 opened this issue Feb 28, 2025 · 6 comments
Assignees
Labels
💥 regression-release Regression from a public release

Comments

@JimWilcox3
Copy link

Description

BindingSource.Sort fails with custom IBindingListView in .NET 9 (worked in .NET 8)

Reproduction Steps

Create a custom class implementing IBindingListView and IBindingList, inheriting from BindingList (see code in Other Information).

Set it as the DataSource of a BindingSource.

Set BindingSource.Sort = "Property desc" where Property exists in the item type.

Observe the result.

Expected behavior

In .NET 8, BindingSource.Sort calls IBindingList.ApplySort, sorting the list.

The list is sorted, and no exception is thrown.

Actual behavior

In .NET 9, it throws "Sort string contains a property that is not in the IBindingList" without calling IBindingList.ApplySort.

Regression?

It worked in .net8

Known Workarounds

In my actual application, I can sort the BindingListView before I pass it to the BindingSource and it works. Once the BindingSource is bound to the DataGridView, you can sort by clicking the column headers. I tried sorting the BindingSource after it was passed to the DataGridView and the Sort property still threw the above error. The problem seems to be with setting the Sort property of the BindingSource.

Configuration

.NET 9.0

Windows 11

x64

Other information

namespace WinFormsApp1
{
using System.ComponentModel;
using System.Windows.Forms;

public class Journal
{
    public DateTime IDate { get; set; }
    public string Name { get; set; }
}

public class SimpleBindingList<T> : BindingList<T>, IBindingListView
{
    public SimpleBindingList(List<T> list) : base(list) { }

    // IBindingListView members
    public void ApplySort(ListSortDescriptionCollection sorts)
    {
        Console.WriteLine("IBindingListView.ApplySort called");
        // Minimal implementation for multi-column sort
    }

    public string Filter { get => ""; set { } }
    public void RemoveFilter() { }
    public ListSortDescriptionCollection SortDescriptions => new ListSortDescriptionCollection();
    public bool SupportsAdvancedSorting => true;
    public bool SupportsFiltering => true;

    // IBindingList overrides
    protected override bool SupportsSortingCore => true;
    protected override bool IsSortedCore => false;
    protected override PropertyDescriptor SortPropertyCore => null;
    protected override ListSortDirection SortDirectionCore => ListSortDirection.Ascending;

    protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection dir)
    {
        Console.WriteLine($"ApplySortCore called with {prop?.Name} {dir}");
        if (prop == null) throw new ArgumentNullException(nameof(prop));
        // Minimal sort logic (not needed to repro the error)
        List<T> list = Items as List<T>;
        if (list != null)
        {
            list.Sort((x, y) => Comparer<object>.Create((a, b) => a.ToString().CompareTo(b.ToString()))
                .Compare(prop.GetValue(x), prop.GetValue(y)));
        }
    }

    // Explicit IBindingList implementation
    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
        Console.WriteLine($"IBindingList.ApplySort called with {property?.Name} {direction}");
        ApplySortCore(property, direction);
    }
}

class Program
{
    static void Main()
    {
        var result = new List<Journal>
    {
        new Journal { IDate = DateTime.Now, Name = "A" },
        new Journal { IDate = DateTime.Now.AddDays(-1), Name = "B" }
    };

        var list = new SimpleBindingList<Journal>(result);
        var bs = new BindingSource { DataSource = list };
        try
        {
            bs.Sort = "IDate desc"; // Should hit ApplySortCore or fail with error
            foreach (Journal item in bs)
            {
                Console.WriteLine($"{item.IDate} - {item.Name}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

}

@dotnet-policy-service dotnet-policy-service bot added the untriaged The team needs to look at this issue in the next triage label Feb 28, 2025
@KalleOlaviNiemitalo
Copy link

The bug was caused by this change: https://github.com/dotnet/winforms/pull/10172/files#diff-be7f960595c89e574536157bf61838085bce49d7e8c48a7f96a58e6764b6a533L994-R997

In the older code string.Compare(current, length - 5, " DESC", 0, 5, true, CultureInfo.InvariantCulture) == 0, the true argument makes the comparison case-insensitive. In the newer code current.EndsWith(" DESC", StringComparison.InvariantCulture), the StringComparison.InvariantCulture argument makes the comparison case-sensitive. It should use StringComparison.InvariantCultureIgnoreCase, instead.

The current code on the main branch still has the same bug:

// Handle ASC and DESC
int length = current.Length;
bool ascending = true;
if (current.EndsWith(" ASC", StringComparison.InvariantCulture))
{
current = current[..(length - 4)].Trim();
}
else if (current.EndsWith(" DESC", StringComparison.InvariantCulture))
{
ascending = false;
current = current[..(length - 5)].Trim();
}

Because the buggy source code of BindingSource is in the dotnet/winforms repository, I think this issue should be moved there.

Known Workarounds

I think you can work around the bug by writing DESC in upper case.

@jeffschwMSFT
Copy link
Member

@merriemcgaw I do not seem to have permission to transfer issues to winforms, but this looks like an issue for winforms to consider

@JimWilcox3
Copy link
Author

Should I re-create this in winforms?

@jeffschwMSFT
Copy link
Member

it is transferrable, I just lack the permission in winforms ;)

@merriemcgaw merriemcgaw transferred this issue from dotnet/runtime Mar 3, 2025
@merriemcgaw merriemcgaw added the 💥 regression-release Regression from a public release label Mar 3, 2025
@merriemcgaw
Copy link
Member

Moved.

@Tanya-Solyanik Tanya-Solyanik removed the untriaged The team needs to look at this issue in the next triage label Mar 3, 2025
@Tanya-Solyanik Tanya-Solyanik self-assigned this Mar 3, 2025
@Zheng-Li01
Copy link
Member

Zheng-Li01 commented Mar 4, 2025

Get the below results.
Image
13061.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💥 regression-release Regression from a public release
Projects
None yet
Development

No branches or pull requests

6 participants