Jak mogę znaleźć ostatni element na liście <>?


172

Poniżej znajduje się wyciąg z mojego kodu:

public class AllIntegerIDs 
{
    public AllIntegerIDs() 
    {            
        m_MessageID = 0;
        m_MessageType = 0;
        m_ClassID = 0;
        m_CategoryID = 0;
        m_MessageText = null;
    }

    ~AllIntegerIDs()
    {
    }

    public void SetIntegerValues (int messageID, int messagetype,
        int classID, int categoryID)
    {
        this.m_MessageID = messageID;
        this.m_MessageType = messagetype;
        this.m_ClassID = classID;
        this.m_CategoryID = categoryID;
    }

    public string m_MessageText;
    public int m_MessageID;
    public int m_MessageType;
    public int m_ClassID;
    public int m_CategoryID;
}

Próbuję użyć następującego kodu w moim kodzie funkcji main ():

List<AllIntegerIDs> integerList = new List<AllIntegerIDs>();

/* some code here that is ised for following assignments*/
{
   integerList.Add(new AllIntegerIDs());
   index++;
   integerList[index].m_MessageID = (int)IntegerIDsSubstring[IntOffset];
   integerList[index].m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
   integerList[index].m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
   integerList[index].m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
   integerList[index].m_MessageText = MessageTextSubstring;
}

Problem jest tutaj: próbuję wydrukować wszystkie elementy z mojej listy przy użyciu pętli for:

for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++) //<----PROBLEM HERE
{
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", integerList[cnt3].m_MessageID,integerList[cnt3].m_MessageType,integerList[cnt3].m_ClassID,integerList[cnt3].m_CategoryID, integerList[cnt3].m_MessageText);
}

Chcę znaleźć ostatni element, aby zrównać cnt3 w mojej pętli for i wydrukować wszystkie wpisy na liście. Każdy element listy jest obiektem klasy AllIntegerIDs, jak wspomniano powyżej w przykładowym kodzie. Jak znaleźć ostatni ważny wpis na liście?

Czy powinienem użyć czegoś takiego jak integerList.Find (integerList []. M_MessageText == null;

Jeśli go użyję, będzie potrzebny indeks, który będzie w zakresie od 0 do dowolnego maksimum. Znaczy, będę musiał użyć innej pętli for, której nie zamierzam używać. Czy istnieje krótszy / lepszy sposób?

Dzięki, Viren


@Viren: Wprowadziłem wcięcie w kodzie, aby był poprawnie wyświetlany. Jeśli pode mną wprowadzałeś zmiany, czy możesz się upewnić, że ich nie cofnąłem?
Sam Harwell

8
Nie jest to związane z twoim pytaniem, ale naprawdę nie powinieneś wdrażać finalizatora, chyba że jest potrzebny.
Brian Rasmussen

Nie jest to związane z pytaniem, ale ze względu na czytelność i łatwość konserwacji sugeruję, abyś to zrobił AllIntegerIDs newItem = new AllIntegerID();, użyj tego do przypisania wszystkich pól, a następnie zadzwoń integerList.Add(newItem). Lub użyj właściwości zamiast pól i użyj składni inicjatora obiektu C # 3.0.
Thorarin

Odpowiedzi:


208

Jeśli chcesz tylko uzyskać dostęp do ostatniej pozycji na liście, możesz to zrobić

if(integerList.Count>0)
{
   var item = integerList[integerList.Count - 1];
}

aby uzyskać całkowitą liczbę elementów na liście, możesz użyć właściwości Count

var itemCount = integerList.Count;

17
@Jared Myślę, że zapomniałeś dodać ten wiersz „if (integerList.Count! = 0)” przed pierwszym wierszem
prabhakaran

21
IMHO to nie zasługuje na najlepszą odpowiedź, to strasznie czyta i pozostawia szansę na błąd, jeśli liczba wynosi zero. Podejście CleanCode ™ polega na użyciu Last/ LastOrDefaultjak wspomniano poniżej.
chillitom

2
Jak wskazano wcześniej, odpowiedź ta nie uwzględnia sytuacji, gdy lista jest pusta i nie należy jej używać IMHO.
merrr

2
@chillitom @merrr Korzystanie z metod rozszerzenia LINQ nie pomaga. Enumerable.Lastzgłosi wyjątek, jeśli lista jest pusta. Jeśli zadzwonisz Enumerable.LastOrDefaulti przekażesz listę typów wartości, wartość domyślna zostanie zwrócona, jeśli lista jest pusta. Więc jeśli otrzymasz 0 z powrotem z a List<int>, nie będziesz wiedział, czy lista była pusta, czy też ostatnia wartość wynosiła 0. Krótko mówiąc, musisz sprawdzić Countmechanizm pobierania, którego zdecydujesz się użyć.
0b101010

4
@chillitom Każdy do swoich. W przypadku, gdy wiesz, że lista jest wypełniona, myślę, że var element = list[list.Count - 1]jest ona bardzo zwięzła i czytelna. Nie ma potrzeby wywoływania metod rozszerzających
0b101010

276

Aby uzyskać ostatni element kolekcji, użyj metod rozszerzających LastOrDefault () i Last ()

var lastItem = integerList.LastOrDefault();

LUB

var lastItem = integerList.Last();

Pamiętaj, aby dodać using System.Linq;, w przeciwnym razie ta metoda nie będzie dostępna.


17
Tak, to najlepszy sposób, Last i LastOrDefault są zoptymalizowane dla List <> s
chillitom

2
@Gusdor Nie widziałem tego udokumentowanego, ale mam tendencję do zwracania się do źródeł (lub używam deasemblera, takiego jak Resharper, dotPeek lub ILSpy) bezpośrednio do tych rzeczy. Stąd widzę, że First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAti ElementAtOrDefaultsą zoptymalizowane pod kątem IList<TSource>, Counti Containssą zoptymalizowane pod kątem ICollection<TSource>i Cast<TResult>jest zoptymalizowany pod kątem IEnumerable<TResult>.
chillitom

8
koniecznie dodajusing System.Linq;
Hybrid

4
@chillitom Metody rozszerzeń udostępnione przez System.Linq.Enumerablenie są tak naprawdę „zoptymalizowane”. Oto kod Enumerable.Lastmetody.
0b101010

4
@chillitom po przeczytaniu źródła System.Linq.Enumerable.Last, zgadzam się z 0b101010 - Last()kod nie jest "zoptymalizowany dla List<>s" - Last()jest po prostu brzydkim opakowaniem, które domyślnie jest ustawione return list[list.Count-1]w przypadku, gdy argumentem jest IList, i iteruje listę do końca w przypadku to nie jest ... co czyni to bardzo słabym rozwiązaniem, jeśli IListjest a LinkedList, ponieważ indeksator przejdzie przez całą listę niepotrzebnie (nie znalazłem zastąpienia iterującego wstecz za Item[]pomocą index> Count / 2 w źródłach c #, YMMV )

20

Przejdźmy do sedna pytania, jak bezpiecznie zająć się ostatnim elementem listy ...

Zarozumiały

List<string> myList = new List<string>();

Następnie

//NOT safe on an empty list!
string myString = myList[myList.Count -1];

//equivalent to the above line when Count is 0, bad index
string otherString = myList[-1];

„count-1” to zły nawyk, chyba że najpierw zagwarantujesz, że lista nie jest pusta.

Nie ma wygodnego sposobu sprawdzenia pustej listy poza zrobieniem tego.

Najkrótszy sposób, jaki przychodzi mi do głowy, to

string myString = (myList.Count != 0) ? myList [ myList.Count-1 ] : "";

możesz pójść na całość i utworzyć delegata, który zawsze zwraca wartość true, i przekazać go do FindLast, który zwróci ostatnią wartość (lub domyślną wartość skonstruowaną, jeśli lista jest pusta). Ta funkcja zaczyna się na końcu listy, więc będzie Wielkie O (1) lub stały czas, mimo że metoda zwykle to O (n).

//somewhere in your codebase, a strange delegate is defined
private static bool alwaysTrue(string in)
{
    return true;
}

//Wherever you are working with the list
string myString = myList.FindLast(alwaysTrue);

Metoda FindLast jest brzydka, jeśli policzysz część delegata, ale musi zostać zadeklarowana tylko w jednym miejscu. Jeśli lista jest pusta, zwróci domyślną skonstruowaną wartość typu listy „” dla łańcucha. Pójście dalej z delegatem alwaysTrue, czyniąc go szablonem zamiast typu string, byłoby bardziej przydatne.


2
Delegata można zastąpić wyrażeniem lambda: myList.FindLast(_unused_variable_name => true);będzie działać niezależnie od typu. Jest to krótsza wersja myList.FindLast(_ => true);, ale uważam, że tylko podkreślenie (lub dowolny inny identyfikator pojedynczego znaku) może być czasami nieco mylące.
Bob


5

Zmiana

for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++)

do

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)

Każdy z nich jest często wygodniejszy w użyciu, ale jest LEKKO wolniejszy.
Eric J.

jeśli używasz Count ... zrób -1 lub otrzymasz błąd indeksu. for (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev

4
Dlatego zmieniłem <= na <. Kod jest poprawny, jak napisano :-)
Eric J.

@Eric: Kiedyś było wolniej, ale trafienie w JIT jest trywialnym przypadkiem, więc zdziwiłbym się, gdyby tego nie zrobili. : nie wiem:
Sam Harwell

1
@IPX Ares: Wydaje się, że nadal stanowi problem, w zależności od typu danych, które iterujesz: stackoverflow.com/questions/365615/ ...
Eric J.

2

Skorzystaj z Countnieruchomości. Ostatni indeks będzie Count - 1.

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)

2

Możesz go znaleźć licząc najpierw liczbę elementów na liście, np

int count = list.Count();

Następnie możesz zindeksować liczbę - 1, aby uzyskać ostatni element na liście, np

int lastNumber = list[count - 1];

2
Nie publikuj zduplikowanych odpowiedzi.
Ian Mercer,

2

W C # 8.0 można uzyskać ostatni element z pełnym wyjaśnieniem operatora ^

List<char> list = ...;
var value = list[^1]; 

// Gets translated to 
var value = list[list.Count - 1];

1

Dlaczego po prostu nie użyć właściwości Count na liście?

for(int cnt3 = 0; cnt3 < integerList.Count; cnt3++)

0

Niezależnie od pierwotnego pytania uzyskasz lepszą wydajność, jeśli będziesz przechwytywać odwołania do zmiennych lokalnych zamiast wielokrotnie indeksować listę:

AllIntegerIDs ids = new AllIntegerIDs();
ids.m_MessageID = (int)IntegerIDsSubstring[IntOffset];
ids.m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
ids.m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
ids.m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
ids.m_MessageText = MessageTextSubstring;
integerList.Add(ids);

A w twojej forpętli:

for (int cnt3 = 0 ; cnt3 < integerList.Count ; cnt3++) //<----PROBLEM HERE
{
   AllIntegerIDs ids = integerList[cnt3];
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n",
      ids.m_MessageID,ids.m_MessageType,ids.m_ClassID,ids.m_CategoryID, ids.m_MessageText);
}

-1

Musiałbym się zgodzić, że dla każdego byłoby o wiele łatwiejsze

foreach(AllIntegerIDs allIntegerIDs in integerList)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", allIntegerIDs.m_MessageID,
allIntegerIDs.m_MessageType,
allIntegerIDs.m_ClassID,
allIntegerIDs.m_CategoryID,
allIntegerIDs.m_MessageText);
}

Sugerowałbym również dodanie właściwości, aby uzyskać dostęp do swoich informacji zamiast pól publicznych, w zależności od wersji .net możesz dodać to jak public int MessageType {get; set;}i pozbyć się m_z pól publicznych, właściwości itp., Ponieważ nie powinno ich tam być.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.