Ordering ObservableCollection with Linq and IComparer
-
11-12-2019 - |
Question
I want to order alphabetically an ObservableCollection because I don't wanna have to create another binding. I've seen we could use Linq and the method OrderBy. I would like also to use a personal IComparer because I will have to organize complex datas (lists into other lists), but VS tells me I can't use my own comparer :
(give you the link of the error because my VS is in french)
http://msdn.microsoft.com/en-gb/library/hxfhx4sy%28v=vs.90%29.aspx
Any ideas ?
private void SortGraphicObjectsAscending(object sender, RoutedEventArgs e)
{
GraphicObjects.OrderBy(graphicObject => graphicObject.Nom, new GraphicObjectComparer(true));
}
And my own comparer (which is already tested)
public class GraphicObjectComparer : IComparer<GraphicObject>
{
int order = 1;
public GraphicObjectComparer(Boolean ascending)
{
if (!ascending)
{
order = -1;
}
}
public GraphicObjectComparer()
{
}
public int Compare(GraphicObject x, GraphicObject y)
{
return String.Compare(x.Nom, y.Nom, false) * order;
}
}
Thanks for your answers. Anyway I have another problem. As Michael said, I use a more complex comparer but for another entity. This entity is displayed with a hierarchical tree, and an object might contain a list of other objects of the same type.
I couldn't test an my GraphicObject becuse I don't have access to the DB (there was no object at the moment). With the tests on my VideoEntity it seems my ObservableCollection is not sorted the way I want (I create another one). I want to reverse it alphabetically but it doesn't work.
public class VideoEntityComparer : IComparer<VideoEntity>
{
int order = 1;
public VideoEntityComparer(Boolean ascending)
{
if (!ascending)
{
this.order = -1; // so descending
}
}
public VideoEntityComparer()
{
}
public int Compare(VideoEntity x, VideoEntity y)
{
if ((x is BaseDirectory && y is BaseDirectory) || (x is BaseSite && y is BaseSite) || (x is VideoEncoder && y is VideoEncoder))
{
return string.Compare(x.Nom, y.Nom, false) * order; // only objects of the same type are sorted alphabetically
}
else if ((x is BaseDirectory && y is BaseSite) || (x is BaseSite && y is VideoEncoder))
{
return -1;
}else
{
return 1;
}
}
}
private void SortDirectoriesDescending(object sender, RoutedEventArgs e)
{
ObservableCollection<BaseDirectory> tempDir = new ObservableCollection<BaseDirectory>(
Directories.OrderBy(directory => directory, new VideoEntityComparer(false)));
Directories = tempDir;
}
PS : by the way, I'm acting on a DependancyProperty. Is it the correct way to do it ? (I'm new on WPF)
Solution
The problem with that line is in the definition of OrderBy
that you're trying to use:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IComparer<TKey> comparer
)
There are two different generic parameters to this method: TSource
and TKey
. TSource
is obvious, it's the same as the TSource
for the source IEnumerable
. The TKey
parameter is the one that the compiler is trying, and failing, to infer because you are trying to use two different types. Your call:
GraphicObjects.OrderBy(graphicObject => graphicObject.Nom, new GraphicObjectComparer(true));
Your first parameter is a Func<GraphicObject, string>
but your second parameter is an IComparer<GraphicObject>
; this means you're using TKey = string
in one place and TKey = GraphicObject
in the other.
The first parameter, the Func<>
delegate, is the "key selector"; it's how you tell OrderBy
which values to sort by. Since your IComparer<>
is sorting based on the entire GraphicObject
instance, that's what you should be selecting as your key:
GraphicObjects.OrderBy(go => go, new GraphicObjectComparer(true));
I'm assuming that your custom comparer object is actually more complex than what you've shown, because your sample comparer is pretty redundant:
var asc = GraphicObjects.OrderBy(go => go.Nom);
var desc = GraphicObjects.OrderByDescending(go => go.Nom);
Also, note that you're sample code isn't actually doing anything with the newly-sorted list, so it's just getting thrown out. LINQ operations never change the source enumerable, they always return a new, transformed copy of it instead.
OTHER TIPS
your comparer
compares GraphicObject
. so your OrderBy
should be
GraphicObjects.OrderBy(graphicObject => graphicObject,
new GraphicObjectComparer(true));
or just use
GraphicObjects.OrderBy(graphicObject => graphicObject.Nom);
BTW, OrderBy
doesn't sort in-place, you should assign the returned IEnumerable<GraphicsObject>
to a variable
The problem is that your Comparer uses GraphicObject, but you compare strings.
If you really need your own ordering, you can use this Comparer:
public class GraphicObjectComparer : IComparer<string>
{
int order = 1;
public GraphicObjectComparer(Boolean ascending)
{
if (!ascending)
{
order = -1;
}
}
public GraphicObjectComparer()
{
}
public int Compare(string x, string y)
{
return String.Compare(x, y, false) * order;
}
}
Or you provide a GraphicObject to your comparer, not a string.
In order to sorted ObservableCollection reflects all the changes in the source collection you can use my ObservableComputations library. Using this library you can code like this:
var sortedGraphicObjects = GraphicObjects.Ordering(
graphicObject => graphicObject.Nom, new GraphicObjectComparer(true));
sortedGraphicObjects is ObservableCollection and reflects all the changes in the GraphicObjects collection and Nom property. Ensure that Nom property notifies of changes through the INotifyPropertyChanged interface.