Est-il possible de transformer un IEnumerable en un IOrderedEnumerable sans utiliser OrderBy?

supposons qu'il existe une méthode d'extension pour commander un IQueryable basée sur plusieurs types de tri (c.-à-d. Tri par diverses propriétés) désigné par un SortMethod enum.

public static IOrderedEnumerable<AClass> OrderByX(this IQueryable<AClass> values,
    SortMethod? sortMethod)
{ 
    IOrderedEnumerable<AClass> queryRes = null;
    switch (sortMethod)
    {
        case SortMethod.Method1:
            queryRes = values.OrderBy(a => a.Property1);
            break;
        case SortMethod.Method2:
            queryRes = values.OrderBy(a => a.Property2);
            break;
        case null:
            queryRes = values.OrderBy(a => a.DefaultProperty);
            break;
        default:
            queryRes = values.OrderBy(a => a.DefaultProperty);
            break;
    }
    return queryRes;
}

Dans le cas où sortMethodnull (c'est à dire où il est spécifié que je n'aime pas à propos de l'ordre des valeurs), il est un moyen à la place de la commande par certains de propriété par défaut, au lieu de simplement les IEnumerator valeurs à travers comme "ordonné" sans avoir à effectuer le réel trier?

j'aimerais avoir la possibilité d'appeler cette extension, et puis, éventuellement, effectuer certaines supplémentaires ThenBy rangements.

22
demandé sur Heisenbug 2013-01-18 21:16:59

3 réponses

Tout ce que vous devez faire pour le cas par défaut est:

queryRes = values.OrderBy(a => 1);

ce sera effectivement une sorte de noop. Parce que L'OrderBy effectue un tri stable, l'ordre d'origine sera maintenu dans le cas où les objets sélectionnés sont égaux. Notez que puisque c'est un IQueryable et pas IEnumerable il est possible que le fournisseur de requête n'effectue pas un tri stable. Dans ce cas, vous devez savoir s'il est important que l'ordre soit maintenu, ou s'il est approprié de simplement dire "Je ne peu importe l'ordre du résultat, tant que je peux appeler ThenBy sur le résultat).

une Autre option, qui vous permet d'éviter le tri est de créer votre propre IOrderedEnumerable mise en oeuvre:

public class NoopOrder<T> : IOrderedEnumerable<T>
{
    private IQueryable<T> source;
    public NoopOrder(IQueryable<T> source)
    {
        this.source = source;
    }

    public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
    {
        if (descending)
        {
            return source.OrderByDescending(keySelector, comparer);
        }
        else
        {
            return source.OrderBy(keySelector, comparer);
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return source.GetEnumerator();
    }
}

Avec votre requête peut être:

queryRes = new NoopOrder<AClass>(values);

Notez que la conséquence de la classe ci-dessus est que si il y a un appel à ThenByThenBy sera effectivement un tri de haut niveau. Il est en effet tourner la suite ThenBy dans un OrderBy appel. (Cela ne devrait pas être surprenant; ThenBy appel CreateOrderedEnumerable méthode, et là ce code appelle OrderBy, EN GROS tourner que ThenBy dans un OrderBy. Du point de vue du tri conceptuel, c'est une façon de dire que "tous les éléments de cette séquence sont égaux aux yeux de ce genre, mais si vous spécifiez que des objets égaux devraient être brisés par quelque chose d'autre, alors faites-le.

une autre façon de penser un "no op sort" est qu'il commande les articles basés dans l'index de la séquence d'entrée. Cela signifie que les articles ne sont pas tous "égaux", cela signifie que l'ordre de la séquence d'entrée être l'ordre final de la séquence de sortie, et comme chaque élément de la séquence d'entrée est toujours plus grand que celui qui le précède, l'ajout de comparaisons supplémentaires ne fera rien, ce qui fait que tout ThenBy appels inutiles. Si ce comportement est désiré, il est encore plus facile à implémenter que le précédent:

public class NoopOrder<T> : IOrderedEnumerable<T>
{
    private IQueryable<T> source;
    public NoopOrder(IQueryable<T> source)
    {
        this.source = source;
    }

    public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
    {
        return new NoopOrder<T>(source);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return source.GetEnumerator();
    }
}
36
répondu Servy 2013-01-18 18:43:50

si vous retournez toujours la même valeur d'index, vous obtiendrez un IOrderedEnumerable qui préserve l'ordre original de la liste:

case null:
     queryRes = values.OrderBy(a => 1);
     break;

Btw, je ne pense pas que ce soit une bonne chose à faire. Vous obtiendrez une collection qui est censé être commandé, mais en fait, il ne l'est pas.

6
répondu Heisenbug 2013-01-18 17:32:48

au final, IOrderedEnumerable existe uniquement pour fournir une structure grammaticale aux méthodes OrderBy()/ThenBy (), vous empêchant d'essayer de démarrer une clause de commande avec ThenBy(). processus. Il ne s'agit pas d'un "marqueur" qui identifie la collection telle qu'elle a été commandée, à moins qu'elle n'ait été commandée par OrderBy(). Donc, la réponse est que, si la méthode de tri nul n'est censé indiquer que l'énumérable est dans certains "par défaut", vous devez spécifier que l'ordre par défaut (comme votre implémentation actuelle). Il est malhonnête de dire que l'énumérable est commandé alors qu'en fait il ne l'est pas, même si, en ne spécifiant pas une méthode SortingMethod, vous déduisez qu'il est "commandé par rien" et ne se soucient pas de l'ordre réel.

le "problème" inhérent à la tentative de marquer simplement la collection telle que commandée en utilisant l'interface est qu'il y a plus dans le processus que le simple tri. En exécutant une chaîne de méthode de commande, telle que myCollection.OrderBy().ThenBy().ThenByDescending(), vous n'êtes pas vraiment le tri de la ramassage à chaque appel; pas encore en tout cas. Vous définissez plutôt le comportement d'une classe "iterator", nommée OrderedEnumerable, qui utilisera les projections et comparaisons que vous définissez dans la chaîne pour effectuer le tri au moment où vous avez besoin d'un élément trié réel.

réponse de Servy, indiquant que OrderBy (x=>1) est un noop et devrait être optimisé à partir de fournisseurs SQL ignore la réalité que cet appel, fait contre un énumérable, fera encore pas mal de travail, et que la plupart des fournisseurs SQL en fait faire optimiser ce type d'appel; OrderBy(x=>1) va, dans la plupart des fournisseurs Linq, produire une requête avec une clause "ORDER BY 1", qui non seulement oblige le fournisseur SQL à effectuer son propre tri, il va effectivement entraîner une modification de l'ordre, parce que dans T-SQL au moins "ORDER BY 1" signifie commander par la première colonne de la liste de sélection.

-1
répondu KeithS 2013-01-18 18:47:19