This article has been localized into Russian by the community.
Списки
C# имеет ряд классов для работы со списками. Они реализуют Интерфейс IList и наиболее популярной реализацией является общий список, часто называемый List<T>. T указывает Тип объектов, содержащихся в списке, который имеет дополнительное преимущество, что компилятор будет проверять и убедится, что вы добавите только объекты нужного типа в список - другими словами, List<T> является типобезопасным.
List
Как уже упоминалось, T обозначает тип и используется для указания типа объектов, которые должны содержаться в списке. В нашем первом примере, я покажу вам, как создать список, который должен содержать строки:
List<string> listOfStrings = new List<string>();
Это создает пустой список, но, легко добавляя в него впоследствии с помощью метода Add:
listOfStrings.Add("a string");
Однако, если вы попробуете добавить что-нибудь, что не является символьной строкой, компилятор немедленно пожалуется на это:
listOfStrings.Add(2);
Error CS1503 Argument 1: cannot convert from 'int' to 'string'
Инициализация списка с элементами
В приведенном выше примере мы просто создали список, а затем добавили в него элемент. Однако C# фактически позволяет создавать список и добавлять в него элементы в пределах одной инструкции, используя метод, называемый инициализаторами коллекций. Давайте посмотрим, как это делается:
List<string> listOfNames = new List<string>()
{
"John Doe",
"Jane Doe",
"Joe Doe"
};
Синтаксис довольно прост: перед обычной конечной точкой с запятой у нас есть комплект фигурных скобок, который, в свою очередь, содержит список значений, которые мы с самого начала хотим, чтобы присутствовали в списке. Поскольку это список строк, начальные объекты, которые мы предоставляем, должны, конечно, иметь строковый тип. Однако то же самое можно сделать для списка других типов, даже если мы используем наши собственные классы, как я продемонстрирую в следующем примере.
Работа с элементами
Есть несколько способов работы с элементами универсального списка, и чтобы показать вам некоторые из них, я создал более крупный пример:
using System;
using System.Collections.Generic;
namespace Lists
{
class Program
{
static void Main(string[] args)
{
List<User> listOfUsers = new List<User>()
{
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 34 },
new User() { Name = "Joe Doe", Age = 8 },
};
for(int i = 0; i < listOfUsers.Count; i++)
{
Console.WriteLine(listOfUsers[i].Name + " is " + listOfUsers[i].Age + " years old");
}
Console.ReadKey();
}
}
class User
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Давайте начинать снизу, где мы определим простой класс для хранения информации о пользователе - просто имя и возраст. Вернемся к верхней части примера, где я изменил наш список, чтобы использовать этот класс пользователя вместо простых строк. Я использую инициализатор коллекции для заполнения списка пользователями - обратите внимание, что синтаксис тот же, что и раньше, только немного сложнее, потому что мы имеем дело с более сложным объектом, чем строка.
Как только список готов, я использую цикл for для его выполнения - чтобы узнать, сколько итераций мы собираемся сделать, я использую свойство Count списка. На каждой итерации я обращаюсь к рассматриваемому пользователю через индексатор списка, используя синтаксис квадратных скобок (например, listOfUsers[i]). Так как у меня есть пользователь, я вывожу имя и возраст.
Добавление, вставка и удаление элементов
Мы уже пытались добавить один элемент в список, но есть больше возможностей для этого. Прежде всего, вы можете вставить элемент вместо добавления - разница в том, что в то время как метод Add всегда добавляет в конец списка, метод Insert позволяет вставить элемент в определенной позиции. Вот пример:
List<string> listOfNames = new List<string>()
{
"Joe Doe"
};
// Insert at the top (index 0)
listOfNames.Insert(0, "John Doe");
// Insert in the middle (index 1)
listOfNames.Insert(1, "Jane Doe");
Сначала мы создаём один элемент в списке, потом добавляем еще два - один вверху списка, второй посередине. Первый параметр метода Insert означает индекс, который определяет порядок элемента. Второй же имеет само значение элемента. Осторожно - будет выдано исключение, если вставить элемент с индексом 3, если в списке меньше элементов.
Добавление нескольких элементов
Так же как у нас есть методы Add и Insert для добавления и вставки одного элемента, также есть соответствующие методы для добавления и вставки нескольких элементов. Они называются AddRange() и InsertRange() и принимают любой тип из коллекции, который как параметр реализует интерфейс IEnumerable - это, например, может быть массив элементов или список, элементы которого вы хотите добавить или вставить в текущий список.
Для примера метода Range давайте сделаем что-нибудь забавное - мы объединим метод AddRange с инициализатором коллекции, чтобы добавить несколько новых имен в существующий список в одном операторе:
listOfNames.AddRange(new string[]
{
"Jenna Doe",
"Another Doe"
});
Мы просто создали массив строк на-лету и немедленно добавили эти элементы в наш список имен из предыдущего примера.
Удаление элементов
Cуществуют три способа удаления одного или нескольких элементов из списка: Remove(), RemoveAt() and RemoveAll().
Метод Remove() принимает лишь один параметр: элемент, который нужно удалить. Это прекрасно подходит, например, для так списка строк или чисел, потому что вы можете просто вписать элемент, который хотите удалить. С другой стороны, если у вас есть список сложных объектов, вам придется сначала найти этот объект, чтобы была ссылка, которую можно передать в метод Remove (). "Давайте разберемся с этим позже" - вот очень простой пример того, как вы можете удалить один элемент с помощью метода Remove:
List<string> listOfNames = new List<string>()
{
"John Doe",
"Jane Doe",
"Joe Doe",
"Another Doe"
};
listOfNames.Remove("Joe Doe");
Метод Remove() просто перебирает список, пока не находит первое вхождение объекта вы указали для удаления и удаляет его - он удаляет только один экземпляр, и если указанный вами пункт в списке, не существует, ошибка не выдается. Метод возвращает true, если удалось удалить элемент, и false, если это не так.
Метод RemoveAt () использует тот факт, что универсальный список основан на индексе, позволяя удалить элемент на основе его индекса/позиции в списке. Например, вы можете удалить первый элемент из списка следующим образом:
listOfNames.RemoveAt(0);
Или последний элемент списка как здесь:
listOfNames.RemoveAt(listOfNames.Count - 1);
Опять же, это удаляет только один элемент, и на этот раз вы должны быть осторожны при предоставлении индекса удаляемого элемента - если вы используете индекс, который выходит за пределы (ниже 0 или выше количества элементов), будет выдано исключение! Таким образом, если Вы не уверены в том, что вы делаете, вы можете обернуть метод RemoveAt() в блок try-catch для обработки исключения (подробно описано в других разделах этого руководства). Метод RemoveAt() ничего не возвращает, поэтому вам придется проверить количество элементов в списке до и после вызова, чтобы решить, был ли он успешным - с другой стороны, если вы знаете, что у вас есть индекс, который существует в списке, о котором вы всегда должны убедиться, то вы всегда можете ожидать, что RemoveAt () будет успешным.
Метод removeall() - это наиболее сложный из удаляющих-методов, но наверняка также самый мощный. Он принимает делегат метода в качестве параметра, и этот метод затем решает, следует ли удалить элемент или нет, возвращая true или false. Он позволяет применять собственную логику при удалении элементов, а также позволяет удалять несколько элементов одновременно. Делегаты будут рассмотрены в другом месте этого учебника, поскольку это большая и сложная тема, но я все равно хочу, чтобы вы почувствовали, насколько крут метод RemoveAll, поэтому вот пример:
List<string> listOfNames = new List<string>()
{
"John Doe",
"Jane Doe",
"Joe Doe",
"Another Doe"
};
listOfNames.RemoveAll(name =>
{
if (name.StartsWith("J"))
return true;
else
return false;
});
В этом примере в качестве параметра метода RemoveAll используется анонимный метод (опять же слишком сложный для объяснения, но он будет рассматриваться в другой главе). Наш анонимный метод довольно прост - он будет вызываться для каждого элемента в списке и иметь параметр с именем name, который, конечно же, является текущим элементом в итерации. Он смотрит на это имя, и если оно начинается с "J", возвращаетсяtrue - в противном случаеfalse . Метод RemoveAll () использует этот ответ (true или false), чтобы решить, следует ли удалять каждый элемент. В конце концов, это оставляет наш первоначальный список только с одним членом Doe: Another Doe.
Сортировка элементов
До сих пор элементы списка, с которыми мы работали, использовались только в том порядке, в котором они были добавлены в список. Тем не менее, вы можете отсортировать элементы определенным образом, например, по алфавиту в случае нашего списка имен. List<T> имеет метод сортировки (), который мы можем использовать для этого:
List<string> listOfNames = new List<string>()
{
"John Doe",
"Jane Doe",
"Joe Doe",
"Another Doe"
};
listOfNames.Sort();
foreach (string name in listOfNames)
Console.WriteLine(name);
Как вы увидите из выходных данных, элементы в списке теперь отсортированы по алфавиту, и если вы хотите, чтобы он был в порядке убывания (от Z до A), просто вызовите метод Reverse () после выполнения сортировки:
listOfNames.Sort();
listOfNames.Reverse();
Итак, сортировать список было довольно легко, верно? Ну, это было в основном так просто, потому что у нас есть список строк, и .NET framework точно знает, как сравнивать две строки. Если у вас есть список чисел, .NET, конечно же, знает, как это сортировать. С другой стороны, у вас может быть список пользовательских объектов (поскольку список<T> может содержать любой объект), который .NET не имеет возможности сравнить. Есть несколько решений этой проблемы, например, реализация интерфейса IComparable или использование LINQ (мы рассмотрим оба позже в этом уроке), но в качестве быстрого исправления мы также можем просто предоставить метод для вызова метода Sort (), чтобы узнать, как два элемента складываются друг против друга, как здесь:
using System;
using System.Collections.Generic;
namespace ListSort
{
class Program
{
static void Main(string[] args)
{
List<User> listOfUsers = new List<User>()
{
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 39 },
new User() { Name = "Joe Doe", Age = 13 },
};
listOfUsers.Sort(CompareUsers);
foreach (User user in listOfUsers)
Console.WriteLine(user.Name + ": " + user.Age + " years old");
}
public static int CompareUsers(User user1, User user2)
{
return user1.Age.CompareTo(user2.Age);
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Это добавило довольно много кода в наш пример, но на самом деле это не слишком сложно. Если мы начнем снизу, я создал очень простой класс пользователя, состоящее из имени и возраста. В середине я объявил метод с именем CompareUsers () - он принимает двух пользователей в качестве параметров, а затем возвращает целое число, которое будет указывать, является ли один элемент "меньшим", "тем же" или "большим" (-1, 0 или 1). Эти значения будут использоваться методом Sort() для перемещения элементов таким образом, чтобы порядок элементов соответствовал тому, что мы хотим. В этом случае я просто использую свойство Age для сравнения, оставляя нам список пользователей, отсортированных по их возрасту.
Резюме
Эта статья является одной из самых длинных в этом уроке, но, надеюсь, вы узнали много нового о списках, потому что чем больше вы программируете, тем больше вы поймете, насколько важны списки и словари. Говоря о словарях, мы обсудим их в следующей статье.