Как я могу найти последний элемент в List <>?

173

Вот выдержка из моего кода:

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

Я пытаюсь использовать следующее в коде функции 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;
}

Проблема здесь: я пытаюсь напечатать все элементы в моем Списке, используя цикл 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);
}

Я хочу найти последний элемент, чтобы приравнять cnt3 в цикле for и распечатать все записи в списке. Каждый элемент в списке является объектом класса AllIntegerIDs, как указано выше в примере кода. Как мне найти последнюю действительную запись в списке?

Должен ли я использовать что-то вроде integerList.Find (integerList []. M_MessageText == null;

Если я использую это, ему понадобится индекс, который будет варьироваться от 0 до любого максимального значения. Значит, мне придется использовать другой цикл for, который я не собираюсь использовать. Есть более короткий / лучший способ?

Спасибо вирен

Zack
источник
@Viren: Я отступил в коде, чтобы он правильно отображался. Если вы внесли изменения под меня, можете ли вы убедиться, что я не отменил их?
Сэм Харвелл
8
Не относится к вашему вопросу, но вы действительно не должны реализовывать финализатор, если он не нужен.
Брайан Расмуссен
Не связанный с вопросом, но для удобочитаемости и удобства сопровождения, я предлагаю вам сделать это AllIntegerIDs newItem = new AllIntegerID();, используйте это, чтобы назначить все поля, а затем вызвать integerList.Add(newItem). Или используйте свойства, а не поля и используйте синтаксис инициализатора объекта C # 3.0.
Торарин

Ответы:

208

Если вы просто хотите получить доступ к последнему элементу в списке, вы можете сделать

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

чтобы получить общее количество элементов в списке, вы можете использовать свойство Count

var itemCount = integerList.Count;
Джаред
источник
17
@Jared Я думаю, что вы забыли добавить эту строку «if (integerList.Count! = 0)» перед первой строкой
prabhakaran
21
ИМХО, это не заслуживает того, чтобы быть лучшим ответом, он читает ужасно и оставляет шанс для ошибки, если счет равен нулю. ™ CleanCode подход будет использовать Last/ LastOrDefaultкак указано ниже.
Chillitom
2
Как указывалось ранее, этот ответ не учитывает ситуацию, когда список пуст и не должен использоваться ИМХО.
merrr
2
@chillitom @merrr Использование методов расширения LINQ не помогает. Enumerable.Lastвыдаст исключение, если список пуст. Если вы позвоните Enumerable.LastOrDefaultи передадите список типов значений, будет возвращено значение по умолчанию, если список пуст. Поэтому, если вы получите 0 от a, List<int>вы не будете знать, был ли список пуст или последнее значение было 0. Короче, вам нужно проверить, Countкакой механизм поиска вы решите использовать.
0b101010
4
@chillitom Каждому свое. В тех случаях, когда вы знаете, что список заполнен, я думаю, что var element = list[list.Count - 1]он очень краткий и читаемый. Не нужно вызывать методы расширения
0b101010
277

Чтобы получить последний элемент коллекции, используйте методы расширения LastOrDefault () и Last ()

var lastItem = integerList.LastOrDefault();

ИЛИ

var lastItem = integerList.Last();

Запомните, чтобы добавить using System.Linq;, или этот метод не будет доступен.

Кундан Сингх Чухан
источник
18
Да, это лучший способ, Last и LastOrDefault оптимизированы для List <> s
chillitom
2
@Gusdor Я не видел этого документированного, но я склонен просто обращаться к источникам (или использовать дизассемблер, такой как Resharper, dotPeek или ILSpy) напрямую для этих вещей. Оттуда я могу видеть , что First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAtи ElementAtOrDefaultоптимизированы для работы IList<TSource>, Countи Containsоптимизированы для работы ICollection<TSource>и Cast<TResult>оптимизирован для IEnumerable<TResult>.
chillitom
8
не забудьте добавитьusing System.Linq;
Hybrid
4
@chillitom Методы расширения, представленные на System.Linq.Enumerableсамом деле, не «оптимизированы». Вот код для Enumerable.Lastметода.
0b101010
4
@chillitom после прочтения источника System.Linq.Enumerable.Last, я согласен с 0b101010 - Last()код не "оптимизирован для List<>s" - Last()это просто уродливая оболочка, которая по умолчанию используется return list[list.Count-1]в случае, если аргумент является IList, и выполняет итерацию по списку до конца в случае, если это не ... делает его очень плохим решением, если IListоно есть LinkedList, так как индексатор просто без необходимости просматривает весь список (я не нашел переопределения, повторяющегося в обратном направлении Item[]с index> Count / 2 в источниках c #, YMMV )
20

Давайте разберемся в корне вопроса, как безопасно обратиться к последнему элементу списка ...

Если предположить,

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

затем

//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» - плохая привычка, если вы сначала не гарантируете, что список не пуст.

Существует не удобный способ проверки пустого списка, кроме как сделать это.

Самый короткий путь, который я могу придумать,

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

Вы можете сделать все возможное и создать делегат, который всегда возвращает true, и передать его FindLast, который вернет последнее значение (или созданный по умолчанию valye, если список пуст). Эта функция запускается в конце списка, поэтому будет Big O (1) или постоянным временем, несмотря на то, что метод обычно 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);

Метод FindLast уродлив, если вы считаете часть делегата, но его нужно объявить только в одном месте. Если список пуст, он вернет созданное по умолчанию значение типа списка "" для строки. Дальнейшее продвижение делегата AlwaysTrue, сделав его шаблоном вместо строкового типа, было бы более полезным.

JFDev
источник
2
Делегат может быть заменен лямбда-выражением: myList.FindLast(_unused_variable_name => true);это будет работать независимо от типа. Укороченная версия есть myList.FindLast(_ => true);, но я считаю, что только подчеркивание (или любой другой односимвольный идентификатор) может иногда быть немного запутанным.
Боб
6
int lastInt = integerList[integerList.Count-1];
Дэн Диплом
источник
5

+ Изменить

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

в

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)
Эрик Дж.
источник
foreach часто более удобен в использовании, но немного медленнее.
Эрик Дж.
если вы используете Count ... сделайте -1, иначе вы получите ошибку индекса. for (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev
4
Вот почему я изменил <= на <. Код правильный, как опубликовано :-)
Эрик Дж.
@Eric: Раньше это было медленнее, но это банальный случай в JIT, так что я бы удивился, если бы они этого не сделали. : dunno:
Сэм Харвелл
1
@IPX Ares: кажется, все еще проблема, в зависимости от типа данных, который вы перебираете: stackoverflow.com/questions/365615/…
Эрик Дж.
2

Используйте Countсобственность. Последний индекс будет Count - 1.

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)
Спенсер Рупорт
источник
2

Вы можете найти его, сначала посчитав количество элементов в списке, например

int count = list.Count();

Затем вы можете проиндексировать счетчик - 1, чтобы получить последний элемент в списке, например

int lastNumber = list[count - 1];
Мухаммед Аашариб Навшад
источник
2
Пожалуйста, не публикуйте повторяющиеся ответы.
Ян Мерсер
2

В C # 8.0 вы можете получить последний элемент с оператором ^ полное объяснение

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

// Gets translated to 
var value = list[list.Count - 1];
Алекс Пэн
источник
1

Почему бы просто не использовать свойство Count в Списке?

for(int cnt3 = 0; cnt3 < integerList.Count; cnt3++)
Brandon
источник
0

Независимо от вашего исходного вопроса, вы получите лучшую производительность, если будете фиксировать ссылки на локальные переменные, а не вносить их в свой список несколько раз:

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

И в вашем forцикле:

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
источник
-1

Я должен был бы согласиться, что foreach будет намного проще, что-то вроде

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

Также я бы посоветовал вам добавить свойства для доступа к вашей информации вместо открытых полей, в зависимости от вашей версии .net вы можете добавить ее, как public int MessageType {get; set;}и избавиться m_от ваших открытых полей, свойств и т. Д., Как это не должно быть там.

Майкл Сиба
источник
-1

Я думаю, что это помогает вам. пожалуйста, проверьте

    TaxRangers[TaxRangers.Count]. max
Sandeep Sandy
источник