Comment puis-je trouver le dernier élément d'une liste <>?


173

Ce qui suit est un extrait de mon code:

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

J'essaie d'utiliser ce qui suit dans mon code de fonction 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;
}

Le problème est ici: j'essaie d'imprimer tous les éléments de ma liste en utilisant une boucle 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);
}

Je veux trouver le dernier élément pour que j'assimile cnt3 dans ma boucle for et que j'imprime toutes les entrées de la liste. Chaque élément de la liste est un objet de la classe AllIntegerIDs comme mentionné ci-dessus dans l'exemple de code. Comment trouver la dernière entrée valide dans la liste?

Dois-je utiliser quelque chose comme integerList.Find (integerList []. M_MessageText == null;

Si j'utilise cela, il faudra un index qui va de 0 à n'importe quel maximum. Cela signifie que je devrai utiliser une autre boucle for que je n'ai pas l'intention d'utiliser. Existe-t-il un moyen plus court / meilleur?

Merci, Viren


@Viren: J'ai mis le code en retrait pour qu'il s'affiche correctement. Si vous avez apporté des modifications sous moi, pouvez-vous vous assurer que je ne les ai pas annulées?
Sam Harwell

8
Pas lié à votre question, mais vous ne devriez vraiment pas implémenter un finaliseur à moins que cela ne soit nécessaire.
Brian Rasmussen

Pas lié à la question, mais pour la lisibilité et la maintenabilité, je vous suggère de le faire AllIntegerIDs newItem = new AllIntegerID();, utilisez cela pour attribuer tous les champs, puis appelez integerList.Add(newItem). Ou utilisez des propriétés plutôt que des champs et utilisez la syntaxe d'initialisation d'objet C # 3.0.
Thorarin

Réponses:


208

Si vous souhaitez simplement accéder au dernier élément de la liste, vous pouvez le faire

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

pour obtenir le nombre total d'éléments dans la liste, vous pouvez utiliser la propriété Count

var itemCount = integerList.Count;

17
@Jared Je pense que vous avez oublié d'ajouter cette ligne "if (integerList.Count! = 0)" avant la première ligne
prabhakaran

21
À mon humble avis, cela ne mérite pas d'être la meilleure réponse, il se lit terriblement et laisse la possibilité d'une erreur si le compte est nul. L' approche CleanCode ™ consisterait à utiliser Last/ LastOrDefaultcomme mentionné ci-dessous.
chillitom

2
Comme indiqué précédemment, cette réponse ne prend pas en compte la situation lorsque la liste est vide et ne doit pas être utilisée à mon humble avis.
merrr

2
@chillitom @merrr L'utilisation des méthodes d'extension LINQ n'aide pas. Enumerable.Lastlèvera une exception si la liste est vide. Si vous appelez Enumerable.LastOrDefaultet transmettez une liste de types de valeurs, la valeur par défaut sera renvoyée si la liste est vide. Donc, si vous récupérez 0 à partir d'un, List<int>vous ne saurez pas si la liste était vide ou si la dernière valeur était 0. En bref, vous devez vérifier le Countmécanisme de récupération que vous décidez d'utiliser.
0b101010

4
@chillitom Chacun son propre. Dans les cas où vous savez qu'une liste est remplie, je pense que var element = list[list.Count - 1]c'est très succinct et lisible. Pas besoin d'invoquer des méthodes d'extension
0b101010

277

Pour obtenir le dernier élément d'une collection, utilisez les méthodes d'extension LastOrDefault () et Last ()

var lastItem = integerList.LastOrDefault();

OU

var lastItem = integerList.Last();

N'oubliez pas d'ajouter using System.Linq;, sinon cette méthode ne sera pas disponible.


18
Oui, c'est le meilleur moyen, Last et LastOrDefault sont optimisés pour List <> s
chillitom

2
@Gusdor Je ne l'ai pas vu documenté mais j'ai tendance à me tourner vers les sources (ou à utiliser un désassembleur comme Resharper, dotPeek ou ILSpy) directement pour ces choses. De là , je peux voir que First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAtet ElementAtOrDefaultsont optimisés pour IList<TSource>, Countet Containssont optimisés pour ICollection<TSource>et Cast<TResult>est optimisé pour IEnumerable<TResult>.
chillitom

8
assurez-vous d'ajouterusing System.Linq;
Hybrid

4
@chillitom Les méthodes d'extension exposées par System.Linq.Enumerablene sont pas vraiment «optimisées». Voici le code de la Enumerable.Lastméthode.
0b101010

4
@chillitom après avoir lu la source de System.Linq.Enumerable.Last, je suis d'accord avec 0b101010 - le Last()code n'est pas "optimisé pour List<>s" - Last()est juste un vilain wrapper, qui est par défaut au return list[list.Count-1]cas où l'argument est un IList, et itère sur la liste jusqu'à la fin au cas où ce n'est pas ... ce qui en fait une très mauvaise solution si le IListest a LinkedList, car l'indexeur passera inutilement par la liste entière (je n'ai pas trouvé de remplacement itérant à l'envers Item[]avec index> Count / 2 dans les sources c #, YMMV )

20

Allons à la racine de la question, comment aborder le dernier élément d'une liste en toute sécurité ...

En supposant

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

ensuite

//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" est une mauvaise habitude à moins que vous ne garantissiez d'abord que la liste n'est pas vide.

Il n'y a pas de moyen pratique de vérifier la liste vide, sauf pour le faire.

Le moyen le plus court auquel je puisse penser est

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

vous pouvez tout mettre en œuvre et créer un délégué qui renvoie toujours vrai, et le transmettre à FindLast, qui renverra la dernière valeur (ou la valeur construite par défaut si la liste est vide). Cette fonction commence à la fin de la liste ainsi sera Big O (1) ou temps constant, bien que la méthode soit normalement 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);

La méthode FindLast est moche si vous comptez la partie déléguée, mais elle n'a besoin d'être déclarée qu'à un seul endroit. Si la liste est vide, elle renverra une valeur construite par défaut du type de liste "" pour chaîne. Il serait plus utile de pousser plus loin le délégué alwaysTrue, en en faisant un modèle au lieu d'un type chaîne.


2
Le délégué peut être remplacé par une expression lambda: myList.FindLast(_unused_variable_name => true);cela fonctionnera quel que soit le type. Une version plus courte est myList.FindLast(_ => true);, mais je trouve que le trait de soulignement (ou tout autre identificateur de caractère unique) peut parfois être un peu déroutant.
Bob


5

Changement

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

à

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

foreach est souvent plus pratique à utiliser, mais est LÉGÈREMENT plus lent.
Eric J.

si vous utilisez Count ... faites un -1 ou vous obtiendrez une erreur d'index. for (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev

4
C'est pourquoi j'ai changé <= en <. Le code est correct tel que publié :-)
Eric J.

@Eric: Avant, c'était plus lent, mais c'est un cas trivial à frapper dans le JIT donc je serais surpris si ce n'est pas le cas maintenant. : dunno:
Sam Harwell

1
@IPX Ares: Cela semble toujours être un problème, selon le type de données que vous itérez: stackoverflow.com/questions/365615/…
Eric J.

2

Utilisez la Countpropriété. Le dernier index sera Count - 1.

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

2

Vous pouvez le trouver en comptant d'abord le nombre d'éléments dans la liste, par exemple

int count = list.Count();

Ensuite, vous pouvez indexer le nombre - 1 pour obtenir le dernier élément de la liste, par exemple

int lastNumber = list[count - 1];

2
Veuillez ne pas publier de réponses en double.
Ian Mercer

2

En C # 8.0, vous pouvez obtenir le dernier élément avec l' explication complète de l' opérateur ^

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

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

1

Pourquoi ne pas simplement utiliser la propriété Count sur la liste?

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

0

Indépendamment de votre question d'origine, vous obtiendrez de meilleures performances si vous capturez des références à des variables locales plutôt que de les indexer plusieurs fois dans votre liste:

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

Et dans votre forboucle:

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

Je devrais convenir qu'un foreach serait beaucoup plus facile quelque chose comme

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

Je vous suggère également d'ajouter des propriétés pour accéder à vos informations au lieu de champs publics, en fonction de votre version .net, vous pouvez l'ajouter comme public int MessageType {get; set;}et vous débarrasser de m_vos champs publics, propriétés, etc.


-1

Je pense que cela vous aide. Vérifiez s'il vous plaît

    TaxRangers[TaxRangers.Count]. max
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.