Como posso encontrar o último elemento em uma Lista <>?

172

A seguir, um extrato do meu código:

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

Estou tentando usar o seguinte no meu código de função 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;
}

O problema está aqui: estou tentando imprimir todos os elementos da minha lista usando um loop 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);
}

Eu quero encontrar o último elemento para que eu iguale cnt3 no meu loop for e imprima todas as entradas na lista. Cada elemento da lista é um objeto da classe AllIntegerIDs, conforme mencionado acima no exemplo de código. Como encontro a última entrada válida na lista?

Devo usar algo como integerList.Find (integerList []. M_MessageText == null;

Se eu usar isso, será necessário um índice que varia de 0 a qualquer valor máximo. Significa que terei que usar outro loop for que não pretendo usar. Existe uma maneira mais curta / melhor?

Obrigado, Viren

zack
fonte
@ Viren: Recuei o código para torná-lo exibido corretamente. Se você fez edições comigo, pode ter certeza de que não as desfiz?
21320 Sam Harwell
8
Não está relacionado à sua pergunta, mas você realmente não deve implementar um finalizador, a menos que seja necessário.
Brian Rasmussen
Não relacionado à pergunta, mas para facilitar a leitura e a manutenção, sugiro que você AllIntegerIDs newItem = new AllIntegerID();use isso para atribuir todos os campos e depois ligue integerList.Add(newItem). Ou use propriedades em vez de campos e use a sintaxe do inicializador de objetos do C # 3.0.
Thorarin 7/08/09

Respostas:

208

Se você quiser apenas acessar o último item da lista, pode fazer

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

para obter o número total de itens na lista, você pode usar a propriedade Count

var itemCount = integerList.Count;
Jared
fonte
17
@Jared Eu acho que você esqueceu de adicionar esta linha "if (integerList.Count = 0!)" Antes da primeira linha
Prabhakaran
21
IMHO isso não merece ser a resposta principal, ele lê muito e deixa a chance de um erro se a contagem for zero. A abordagem CleanCode ™ seria usar Last/ LastOrDefaultconforme mencionado abaixo.
chillitom
2
Como apontado anteriormente, esta resposta não leva em consideração a situação quando a lista está vazia e não deve ser usada no IMHO.
merrr
2
@chillitom @merrr O uso dos métodos de extensão LINQ não ajuda. Enumerable.Lastlançará uma exceção se a lista estiver vazia. Se você chamar Enumerable.LastOrDefaulte passar uma lista de tipos de valor, o valor padrão será retornado se a lista estiver vazia. Portanto, se você receber 0 de volta de um List<int>, não saberá se a lista estava vazia ou se o último valor foi 0. Em resumo, você precisa verificar o Countmecanismo de recuperação que decidir usar.
precisa saber é o seguinte
4
@chillitom Cada um por conta própria. Nos casos em que você sabe que uma lista é preenchida, acho que var element = list[list.Count - 1]é muito sucinta e legível. Não há necessidade de invocar métodos de extensão
0b101010 07/11
277

Para obter o último item de uma coleção, use os métodos de extensão LastOrDefault () e Last ()

var lastItem = integerList.LastOrDefault();

OU

var lastItem = integerList.Last();

Lembre-se de adicionar using System.Linq; ou esse método não estará disponível.

Kundan Singh Chouhan
fonte
17
Yep este é o melhor caminho, Última e LastOrDefault são otimizados para List <> s
chillitom
2
@ Gusdor Eu não o vi documentado, mas eu tendem a me voltar para as fontes (ou usar um desmontador como Resharper, dotPeek ou ILSpy) diretamente para essas coisas. De lá eu posso ver que First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAte ElementAtOrDefaultsão otimizados para IList<TSource>, Counte Containssão otimizados para ICollection<TSource>e Cast<TResult>é otimizado para IEnumerable<TResult>.
Chillitom
8
certifique-se de adicionarusing System.Linq;
Híbrido
4
@ chillitom Os métodos de extensão expostos por System.Linq.Enumerablenão são realmente 'otimizados'. Aqui está o código para o Enumerable.Lastmétodo
0101010
4
@chillitom depois de ler a fonte System.Linq.Enumerable.Last, eu concordo com 0b101010 - o Last()código não é "otimizado para List<>s" - Last()é apenas um invólucro feio, cujo padrão é return list[list.Count-1]no caso do argumento é um IList, e itera sobre a lista até o fim no caso não é ... tornando-a uma solução muito ruim se IListfor a LinkedList, pois o indexador passará por toda a lista desnecessariamente (não encontrei uma substituição repetindo o Item[]índice> Count / 2 nas fontes c #, YMMV )
20

Vamos chegar à raiz da questão, como abordar o último elemento de uma lista com segurança ...

Assumindo

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

Então

//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];

"contagem 1" é um mau hábito, a menos que você primeiro garanta que a lista não esteja vazia.

Não há uma maneira conveniente de verificar a lista vazia, exceto para fazê-lo.

A maneira mais curta que consigo pensar é

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

você pode fazer tudo e criar um delegado que sempre retorna true e passá-lo para FindLast, que retornará o último valor (ou valor construído padrão, se a lista estiver vazia). Esta função começa no final da lista, então será Big O (1) ou tempo constante, apesar do método normalmente ser 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);

O método FindLast é feio se você contar a parte do delegado, mas ele precisa ser declarado apenas um local. Se a lista estiver vazia, ele retornará um valor construído padrão do tipo de lista "" para string. Levar o delegado alwaysTrue um passo adiante, tornando-o um modelo em vez do tipo de string, seria mais útil.

JFDev
fonte
2
O delegado pode ser substituído por uma expressão lambda: myList.FindLast(_unused_variable_name => true);Isso funcionará independentemente do tipo. É uma versão mais curta myList.FindLast(_ => true);, mas acho que apenas o sublinhado (ou qualquer outro identificador de caractere único) pode ser um pouco confuso às vezes.
29413 Bob
6
int lastInt = integerList[integerList.Count-1];
Dan Diplo
fonte
5

mudança

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

para

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)
Eric J.
fonte
o foreach geralmente é mais conveniente de usar, mas é UM pouco mais lento.
Eric J.
se estiver usando Count ... faça -1 ou você receberá um erro de índice. para (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev
4
Por isso mudei <= para <. O código está correto conforme publicado :-)
Eric J.
@ Eric: Costumava ser mais lento, mas é um caso trivial de ser atingido no JIT, então eu ficaria surpreso se não o fizessem até agora. : dunno:
Sam Harwell
1
@IPX Ares: Parece ainda ser um problema, dependendo do tipo de dados que estão a iteração: stackoverflow.com/questions/365615/...
Eric J.
2

Use a Countpropriedade O último índice será Count - 1.

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)
Spencer Ruport
fonte
2

Você pode encontrá-lo contando primeiro o número de elementos na lista, por exemplo

int count = list.Count();

Então você pode indexar a contagem - 1 para obter o último elemento da lista, por exemplo

int lastNumber = list[count - 1];
Muhammad Aasharib Nawshad
fonte
2
Não poste respostas duplicadas.
22618 Ian Ian,
2

No C # 8.0, você pode obter o último item com a explicação completa do operador ^

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

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

Por que não usar apenas a propriedade Count na lista?

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

Independentemente da sua pergunta original, você obterá melhor desempenho se capturar referências a variáveis ​​locais em vez de indexar na sua lista várias vezes:

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 no seu forloop:

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);
}
dahlbyk
fonte
-1

Eu teria que concordar que um foreach seria muito mais fácil, algo como

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

Além disso, eu sugiro que você adicione propriedades para acessar suas informações em vez de campos públicos, dependendo da sua versão .net, você pode adicioná-las como public int MessageType {get; set;}e se livrar dos m_campos públicos, propriedades etc., pois ela não deveria estar lá.

Michael Ciba
fonte
-1

Eu acho que isso ajuda você. por favor, verifique

    TaxRangers[TaxRangers.Count]. max
sandeep sand
fonte