TOC

This article is currently in the process of being translated into Vietnamese (~26% done).

Collections:

Lists

C# có một tập các lớp làm việc với danh sách. Chúng thực hiện interface IList và phổ biến nhất là generic list, có dạng List<T>. T là kiểu đối tượng của các phần tử bên trong danh sách mà giúp cho trình biên dịch kiểm tra và đảm bảo rằng bạn thêm một đối tượng với đùng kiểu dữ liệu vào trong danh sách - hay nói cách khác List<T> đảm bảo tính toàn vẹn kiểu dữ liệu.

List giống với lớp ArrayList, xuất hiện trước khi C# hỗ trợ danh sách genetic. Vậy nên bạn cũng sẽ thấy List có thể làm nhiều thứ tương tự Array (cũng thực hiện IList), nhưng trong nhiều tình huống thig List đơn giản và dễ làm việc hơn. Ví dụ, bạn có thể tạo ra nó và .NET sẽ tự động mở rộng nó để tương thích với số phần tử mà bạn thêm vào danh sách.

Như đã nói, T đại diện cho kiểu dữ liệu và được dùng để chỉ định kiểu đối tượng bạn muốn danh sách chứa bên trong. Trong ví dụ đầu tiên của chúng ta, tôi sẽ chỉ cho bạn cách tạo ra một danh sách chứa chuỗi:

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

Câu lệnh này tạo ra một danh sách rỗng nhưng để thêm phần tử vào danh sách thì rất dễ với phương thức Add:

listOfStrings.Add("a string");

Tuy nhiên, nếu bạn cố thêm cái gì đó mà không phải là chuỗi thì trình biên dịch sẽ báo lỗi:

listOfStrings.Add(2);
Error   CS1503  Argument 1: cannot convert from 'int' to 'string'

Initializing a list with items

Trong ví dụ trên, chúng ta chỉ tạo ra một danh sách và sau đó thêm giá trị vào danh sách đó. Tuy nhiên, C# cho phép bạn tạo ra danh sách VÀ thêm giá trị vào nó trong cùng câu lệnh, gọi là khởi tạo danh sách. Hãy xem cách làm thế nào:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe"
};

Cú pháp khá đơn giản: Trước khi kết thúc băng dấu ; thì chúng ta thiết lập trong {} một danh sách các giá trị chúng ta muốn có trong danh sách lúc ban đầu; Vì đây là một danh sách chuỗi nên đương nhiên các giá trị sẽ là kiểu chuỗi. Tuy nhiên, chúng ta có thể làm tương tự với các kiểu dữ liệu khác hay thậm chí lớp tự định nghĩa và tôi sẽ minh họa trong ví dụ tiếp theo.

Working with the items

Có nhiều cách để làm việc với các phần tử trong danh sách generic và để chỉ cho các bạn một vài trong số đó, tôi tạo ra một ví dụ lớn hơn:

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

Hãy bắt đầu từ dưới lên, chúng ta đinh nghĩa một lớp đơn giản để chứa thông tin về User - chỉ có name và age. Phía trên thì chúng ta thay danh sách User thay vì danh sách chuỗi. Tôi khởi tạo danh sách user - chú ý cú pháp giống trước đây, chỉ hơi phức tạp hơn một chút vì chúng ta làm việc với đối tượng phức tạp hơn chuỗi.

Once we have the list ready, I use a for loop to run through it - to know how many iterations we're going to do, I use the Count property of the List. On each iteration, I access the user in question through the indexer of the list, using the square bracket syntax (e.g. listOfUsers[i]). Once I have the user, I output name and age.

Adding, Inserting & Removing items

We already tried adding a single item to a list, but there are more options for doing this. First of all, you can Insert an item instead of adding it - the difference is that while the Add method always adds to the end of the list, the Insert method allows you to insert an item at a specific position. Here's an example:

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

We start the list of with just one item, but then we insert two more items, first at the top of the list and then in the middle. The first parameter of the Insert method is the index where we want to insert the item. Be careful though - an exception will be thrown if you try to insert an item at index 3, if the list has less items!

Adding multiple items

Just like we have the Add and Insert methods for adding a single item, there are also corresponding methods for adding and inserting multiple items. They are called AddRange() and InsertRange() and accepts any type of collection which implements the IEnumerable interface as a parameter - this could be e.g. an array of items or another list, which items you want to add or insert into the current list.

As an example on the Range methods, let's do something fun - we combine the AddRange method with a collection initializer to add several new names to an existing list in a single statement:

listOfNames.AddRange(new string[]
    {
"Jenna Doe",
"Another Doe"
    });

We simply create an array of strings on-the-fly and immediately append its items to our list of names from the previous example.

Removing items

There are currently three methods at your disposal when you want to remove one or several items from a list: Remove(), RemoveAt() and RemoveAll().

The Remove() method takes just one parameter: The item you want to remove. This is great for e.g. a list of strings or integers, because you can simply just write the item you want to remove. On the other hand, if you have a list of complex objects, you would have to find that object first, to have a reference you could pass to the Remove() method.¨Let's deal with that later - here's a very basic example on how you can remove a single item with the Remove() method:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe",
    "Another Doe"
};

listOfNames.Remove("Joe Doe");

The Remove() method simply iterates through the list until it finds the first instance of the object you specified for removal, and them removes it - it only removes one instance, and if you specify an item in the list which doesn't exist, no error is thrown. The method returns true if it was able to remove an item and false if it wasn't.

The RemoveAt() method takes advantage of the fact that the generic list is index based by allowing you to remove an item based on its index/position in the list. For instance, you could remove the first item from the list like this:

listOfNames.RemoveAt(0);

Or the last item in the list like this:

listOfNames.RemoveAt(listOfNames.Count - 1);

Again, this only removes a single item and this time, you should be careful when providing the index of the item to be removed - if you use an index that falls out of bounds (lower than 0 or higher than the amount of items) an exception will be thrown! So, unless you're sure of what you're doing, you might want to wrap the RemoveAt() method in a try-catch block for handling the exception (explained in detail elsewhere in this tutorial). The RemoveAt() method doesn't return anything, so you will have to check the amount of items in the list before and after the call, to decide if it was successful - on the other hand, if you know that you have an index that exists in the list, which you should always make sure of, then you can always expect RemoveAt() to be successful.

The RemoveAll() is the most complex of the remove-methods, but definitely also the most powerful. It takes a delegate to a method as its parameter and this method then decides whether an item should be removed or not by returning true or false. This allows you to apply your own logic when removing items and it also allows you to remove more than one item at a time. Delegates will be treated elsewhere in this tutorial, because it's a big and complex subject, but I still want you to get a feel of how cool the RemoveAll method is, so here's an example:

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

In this example, we use an anonymous method (again too complex to be explained here, but it will be treated in another chapter) as a parameter for the RemoveAll method. Our anonymous method is pretty simple - it will be called for each item in the list and have a parameter called name, which is of course the current item in the iteration. It looks at this name and if it starts with "J", true is returned - otherwise false. The RemoveAll() method uses this response (true or false) to decide if each item should be removed or not. In the end, this leaves our initial list with just one Doe member left: Another Doe.

Sorting List Items

So far, the items in the list we have worked with has just been used in the order in which they were added to the list. However, you may want to have the items sorted in a specific way, e.g. alphabetically in the case of our list-of-names. The List<T> has a Sort() method which we can use for this:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe",
    "Another Doe"
};
listOfNames.Sort();
foreach (string name in listOfNames)
    Console.WriteLine(name);

As you will see from the output, the items in the list have now been sorted alphabetically, and if you want it in descending order instead (from Z to A), simply call the Reverse() method after you perform the sort:

listOfNames.Sort();
listOfNames.Reverse();

So sorting a list was pretty easy, right? Well, it was mainly so easy because we have a list of strings and the .NET framework knows exactly how to compare two strings. If you have a list of numbers, .NET will, of course, know how to sort that as well. On the other hand, you might have a list of custom objects (since the List<T> can contain any object) which .NET doesn't have a chance of knowing how to compare. There are several solutions to this problem, e.g. implementing the IComparable interface or using LINQ (we'll look into both later in this tutorial), but as a quick-fix, we can also just provide a method for the Sort() method to call, to learn how two items stack up against each other, like this:

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

This added quite a bit of code to our example, but it's actually not too complicated. If we start from the bottom, I have created a very simple User class, consisting of a name and an age. In the middle, I have declared a method called CompareUsers() - it takes two users as parameters and then returns an integer, which will indicate whether one item is "smaller", "the same" or "larger" (-1, 0 or 1). These values will be used by the Sort() method to move the items around so that the order of items will match what we want. In this case, I simply use the Age property for comparison, essentially leaving us with a list of users sorted by their age.

Summary

This article is one of the longer ones in this tutorial, but hopefully you learned a lot about lists, because the more programming you do, the more you will realize how important lists and dictionaries are. Speaking of dictionaries, we'll discuss them in the next article.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!