Come posso trovare l'ultimo elemento in un Elenco <>?


173

Quello che segue è un estratto dal mio codice:

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;
}

Sto cercando di utilizzare quanto segue nel mio codice funzione principale ():

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;
}

Il problema è qui: sto cercando di stampare tutti gli elementi nella mia lista usando un ciclo 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);
}

Voglio trovare l'ultimo elemento in modo da equiparare cnt3 nel mio ciclo for e stampare tutte le voci nell'elenco. Ogni elemento nell'elenco è un oggetto della classe AllIntegerIDs come menzionato sopra nell'esempio di codice. Come trovo l'ultima voce valida nell'elenco?

Dovrei usare qualcosa come integerList.Find (integerList []. M_MessageText == null;

Se lo uso avrà bisogno di un indice che varierà da 0 a qualunque sia il massimo. Significa che dovrò usarne un altro per loop che non intendo usare. C'è un modo più breve / migliore?

Grazie Viren


@Viren: ho indentato il codice per farlo mostrare correttamente. Se hai apportato modifiche sotto di me, puoi assicurarti che non le abbia annullate?
Sam Harwell,

8
Non correlato alla tua domanda, ma in realtà non dovresti implementare un finalizzatore se non è necessario.
Brian Rasmussen,

Non correlato alla domanda, ma per leggibilità e manutenibilità, ti suggerisco di farlo AllIntegerIDs newItem = new AllIntegerID();, utilizzarlo per assegnare tutti i campi e quindi chiamare integerList.Add(newItem). Oppure usa le proprietà anziché i campi e usa la sintassi dell'inizializzatore di oggetti C # 3.0.
Thorarin,

Risposte:


208

Se vuoi solo accedere all'ultimo elemento nell'elenco puoi farlo

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

per ottenere il numero totale di elementi nell'elenco è possibile utilizzare la proprietà Count

var itemCount = integerList.Count;

17
@Jared Penso che hai dimenticato di aggiungere questa riga "if (integerList.Count! = 0)" prima della prima riga
prabhakaran,

21
IMHO questo non merita di essere la risposta migliore, legge terribilmente e lascia la possibilità di un errore se il conteggio è zero. L' approccio CleanCode ™ sarebbe quello di utilizzare Last/ LastOrDefaultcome indicato di seguito.
Chillitom,

2
Come sottolineato in precedenza, questa risposta non tiene conto della situazione quando l'elenco è vuoto e non deve essere utilizzato IMHO.
martedì

2
@chillitom @merrr L'uso dei metodi di estensione LINQ non aiuta. Enumerable.Lastgenererà un'eccezione se l'elenco è vuoto. Se si chiama Enumerable.LastOrDefaulte si passa a un elenco di tipi di valore, il valore predefinito verrà restituito se l'elenco è vuoto. Quindi, se si ottiene 0 indietro da un List<int>non si saprà se l'elenco era vuoto o l'ultimo valore era 0. In breve, è necessario verificare il Countmeccanismo di recupero che si decide di utilizzare.
0b101010

4
@chillitom Ciascuno per conto proprio. Nei casi in cui sai che un elenco è popolato penso che var element = list[list.Count - 1]sia molto sintetico e leggibile. Non è necessario invocare metodi di estensione
0b101010,

277

Per ottenere l'ultimo elemento di una raccolta utilizzare i metodi di estensione LastOrDefault () e Last ()

var lastItem = integerList.LastOrDefault();

O

var lastItem = integerList.Last();

Ricorda di aggiungere using System.Linq;o questo metodo non sarà disponibile.


18
Sì, questo è il modo migliore, Last e LastOrDefault sono ottimizzati per List <> s
chillitom

2
@Gusdor Non l'ho visto documentato, ma tendo a rivolgersi alle fonti (o utilizzare un disassemblatore come Resharper, dotPeek o ILSpy) direttamente per queste cose. Da lì posso vedere che First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAte ElementAtOrDefaultsono ottimizzati per IList<TSource>, Counte Containssono ottimizzati per ICollection<TSource>e Cast<TResult>è ottimizzato per IEnumerable<TResult>.
Chillitom,

8
assicurati di aggiungereusing System.Linq;
Hybrid

4
@chillitom I metodi di estensione esposti System.Linq.Enumerablenon sono realmente "ottimizzati". Ecco il codice per il Enumerable.Lastmetodo.
0b101010

4
@chillitom dopo aver letto la fonte di System.Linq.Enumerable.Last, sono d'accordo con 0b101010 - il Last()codice non è "ottimizzato per List<>s" - Last()è solo un brutto wrapper, che per impostazione predefinita return list[list.Count-1]nel caso l'argomento è un IList, e scorre l'elenco fino alla fine nel caso non è ... rendendolo una soluzione molto scadente se IListè a LinkedList, poiché l'indicizzatore passerà inutilmente l'intero elenco (non ho trovato una sostituzione iterando all'indietro Item[]con index> Count / 2 nelle fonti c #, YMMV )

20

Andiamo alla radice della domanda, come affrontare l'ultimo elemento di un elenco in modo sicuro ...

assumendo

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

Poi

//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" è una cattiva abitudine a meno che tu non garantisca prima che l'elenco non sia vuoto.

Non esiste un modo conveniente per cercare l'elenco vuoto se non per farlo.

Il modo più breve a cui riesco a pensare è

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

potresti fare tutto e creare un delegato che restituisce sempre true e passarlo a FindLast, che restituirà l'ultimo valore (o valore predefinito creato se l'elenco è vuoto). Questa funzione inizia alla fine dell'elenco, quindi sarà Big O (1) o tempo costante, nonostante il metodo sia normalmente 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);

Il metodo FindLast è brutto se si conta la parte delegata, ma deve essere dichiarato solo in un posto. Se l'elenco è vuoto, verrà restituito un valore predefinito predefinito del tipo di elenco "" per stringa. Fare un ulteriore passo avanti con il delegato alwaysTrue, trasformandolo in un modello anziché in un tipo di stringa, sarebbe più utile.


2
Il delegato può essere sostituito con un'espressione lambda: myList.FindLast(_unused_variable_name => true);funzionerà indipendentemente dal tipo. Una versione più corta è myList.FindLast(_ => true);, ma trovo che solo il trattino basso (o qualsiasi altro identificatore di singolo carattere) possa essere un po 'confuso a volte.
Bob


5

Modificare

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

per

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

foreach è spesso più comodo da usare, ma è LEGGERMENTE più lento.
Eric J.

se usi Count ... fai un -1 o otterrai un errore di indice. per (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev

4
Ecco perché ho cambiato <= in <. Il codice è corretto come pubblicato :-)
Eric J.

@Eric: Prima era più lento, ma è un caso banale colpire nella squadra, quindi sarei sorpreso se non lo fossero ormai. : non so:
Sam Harwell,

1
@IPX Ares: sembra essere ancora un problema, a seconda del tipo di dati che stai ripetendo: stackoverflow.com/questions/365615/…
Eric J.

2

Usa la Countproprietà L'ultimo indice sarà Count - 1.

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

2

Puoi trovarlo contando prima il numero di elementi nell'elenco, ad es

int count = list.Count();

Quindi è possibile indicizzare il conteggio - 1 per ottenere l'ultimo elemento nell'elenco, ad es

int lastNumber = list[count - 1];

2
Si prega di non pubblicare risposte duplicate.
Ian Mercer,

2

In C # 8.0 è possibile ottenere l'ultimo elemento con la spiegazione completa dell'operatore ^

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

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

1

Perché non usare semplicemente la proprietà Count nell'elenco?

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

0

Indipendentemente dalla tua domanda originale, otterrai prestazioni migliori se acquisisci riferimenti a variabili locali anziché indicizzarli nell'elenco più volte:

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);

E nel tuo forciclo:

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

Dovrei essere d'accordo sul fatto che una foreach sarebbe molto più facile qualcosa del genere

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);
}

Inoltre, ti suggerisco di aggiungere proprietà per accedere alle tue informazioni anziché ai campi pubblici, a seconda della tua versione .net puoi aggiungerle come public int MessageType {get; set;}e sbarazzarti dei m_tuoi campi pubblici, proprietà ecc. Come non dovrebbe essere lì.


-1

Penso che questo ti aiuti. si prega di controllare

    TaxRangers[TaxRangers.Count]. max
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.