2010-05-05 12 views
7

Mam dość złożony scenariusz i muszę się upewnić, że pozycje, które mam na liście, są sortowane.C# jak sortować listę bez ręcznego wdrażania IComparable?

Po pierwsze pozycje na liście oparte są na strukturze zawierającej podstrukturę.

Na przykład:

public struct topLevelItem 
{ 
public custStruct subLevelItem; 
} 

public struct custStruct 
{ 
    public string DeliveryTime; 
} 

Teraz mam listę składającą się z topLevelItems na przykład:

var items = new List<topLevelItem>(); 

potrzebuję sposób sortowania na ASC DeliveryTime. Co dodatkowo zwiększa złożoność, to pole DeliveryTime jest ciągiem znaków. Ponieważ te struktury są częścią interfejsu API wielokrotnego użytku, nie mogę zmodyfikować tego pola do elementu DateTime, ani nie mogę zaimplementować opcji IComparable w klasie topLevelItem.

Jakieś pomysły, jak to zrobić?

Dziękuję

Odpowiedz

6

Brzmi jak trzeba zdobyć daty sortowania chociaż data jest reprezentowany jako ciąg canonicalized, tak? Cóż, można użyć OrderBy operatora LINQ, ale trzeba będzie analizować ciąg do daty, aby osiągnąć właściwe rezultaty:

items = items.OrderBy(item => DateTime.Parse(item.subLevelItem.DeliveryTime)) 
      .ToList(); 

Aktualizacja:

Dodałem to na kompletna - prawdziwym przykładem tego, jak używam ParseExact z kulturą niezmienną:

var returnMessagesSorted = returnMessages.OrderBy((item => DateTime.ParseExact(item.EnvelopeInfo.DeliveryTime, ISDSFunctions.GetSolutionDateTimeFormat(), CultureInfo.InvariantCulture))); 
return returnMessagesSorted.ToList(); 

zawsze można zaimplementować osobną klasę IComparer, to nie jest zabawa, ale to działa dobrze:

public class TopLevelItemComparer : IComparer<topLevelItem> 
{ 
    public int Compare(topLevelItem x, topLevelItem y) 
    { 
     return DateTime.Parse(x.subLevelItem.DeliveryTime).CompareTo(
      DateTime.Parse(y.subLevelItem.DeliveryTime)); 
    } 
} 

items.Sort(new TopLevelItemComparer()); 

Należy pamiętać, że większość Sort() metody w ramach .NET zaakceptować IComparer lub IComparer<T> który pozwala na przedefiniowanie semantyki porównania dla każdego typu. Zwykle po prostu używasz Comparer<T>.Default - lub użyj przeciążenia, które zasadniczo dostarcza ci to.

+0

brzmi dobrze, myślę, że dodanie ciąg formatu (z parseExact) i niezmienna kultury do mix nie zaszkodzi? –

+0

@JL: Im bardziej konkretny kod, tym lepiej. Pozwalanie funkcji Parse() na automatyczne wykrywanie formatu daty nie jest świetnym pomysłem, gdy można tego uniknąć. Wyjątkiem jest pisanie kodu umiędzynarodowionego i korzystanie z bieżących ustawień kultury wątku. – LBushkin

4

Używanie LINQ:

items = items.OrderBy(item => item.subLevelItem.DeliveryTime).ToList(); 
+0

Chyba muszę zrobić jakiś ciąg do konwersji datetime zbyt ? –

+0

To tworzy całkowicie nową listę, nie sortując istniejącej na miejscu. –

+0

Prawdopodobnie, ale jeśli twój ciąg znaków jest w standardowym formacie, możesz po prostu zadzwonić do 'DateTime.Parse' –

7

Utwórz nowy typ, który implementuje IComparer i użyć instancji nim porównać obiekty.

public class topLevelItemComparer : IComparer<topLevelItem> 
{ 
    public int Compare(topLevelItem a, topLevelItem b) 
    { 
     // Compare and return here. 
    } 
} 

Można połączyć Sort() tak:

var items = new List<topLevelItem>(); 
// Fill the List 
items.Sort(new topLevelItemComparer()); 
+0

Zawsze wydawało mi się dziwne, że IComparer jest nadal preferowanym sposobem na to. "Func " byłoby o wiele ładniejsze. –

+0

@Matt Greer, Nie wiem o "preferowanym", ale możesz sortować za pomocą Delegata porównania (http://msdn.microsoft.com/en-us/library/w56d4y5z.aspx). –

+0

@Matthew Flaschen, ah miłe, nie wiedziałem o tym. Dzięki. –

2

Miałem ten problem, zanim raz zaimplementowałem LambdaComparer, który porównał w oparciu o dowolne wyrażenie lambda. Nie dokładny kod, ale coś w tym stylu:

public class LambdaComparer : IComparer<T> 
{ 
    private Func<T,T,int> _func; 

    public LambdaComparer(Func<T,T,int> function) 
    { 
     _func = function; 
    } 

    public int Compare(T x, T y) 
    { 
     return _func(x,y); 
    } 
} 

Dużą zaletą jest to, że otrzymujesz ładną wielokrotność fragmentu kodu.

4

Jeśli chcesz wykonują w miejscu sortowania a następnie można użyć przeciążenie Sort że trwa Comparison<T> argumentu i przekazać anonimową funkcję/lambda:

items.Sort((x, y) => DateTime.Parse(x.subLevelItem.DeliveryTime).CompareTo(
        DateTime.Parse(y.subLevelItem.DeliveryTime))); 

Jeśli wolisz, aby utworzyć nowy posortowane sekwencję zamiast sortowania na miejscu, najprawdopodobniej jest to droga LINQ OrderBy, o czym już wspominali inni.

+0

+1 Nie zdałem sobie sprawy, że lista miała przeciążenie sortujące, które zabiera delegata. Wiele innych miejsc w strukturze akceptuje tylko IComparer, więc trochę zaskoczony, widząc go tutaj. – Stephan

+0

Nie zdawałem sobie z tego sprawy. Dokładnie tego też szukałem –

1

Aby posortować listę samego items:

Comparison<topLevelItem> itemComparison = (x, y) => { 
    DateTime dx; 
    DateTime dy; 

    bool xParsed = DateTime.TryParse(x.subLevelItem.DeliveryTime, out dx); 
    bool yParsed = DateTime.TryParse(y.subLevelItem.DeliveryTime, out dy); 

    if (xParsed && yParsed) 
     return dx.CompareTo(dy); 
    else if (xParsed) 
     return -1; // or 1, if you want invalid strings to come first 
    else if (yParsed) 
     return 1; // or -1, if you want invalid strings to come first 
    else 
     // simple string comparison 
     return x.subLevelItem.DeliveryTime.CompareTo(y.subLevelItem.DeliveryTime); 
}; 

items.Sort(itemComparison); 

Takie podejście ma tę zaletę:

  1. sortowanie listy na miejscu (to znaczy, jeśli właściwie chcą zawartość listy posortowane -miejsce)
  2. Sortowanie według rzeczywistych wartości DateTime zamiast ciągów znaków, ALE ...
  3. Nie rzuca wyjątek, jeśli łańcuch nie reprezentuje prawidłowej DateTime (zasadniczo wszystkie nieprawidłowe ciągi skończy się na jednej stronie listy)