Nó là gì?
Ngoại lệ này có nghĩa là bạn đang cố gắng truy cập một mục bộ sưu tập theo chỉ mục, sử dụng chỉ mục không hợp lệ. Một chỉ mục không hợp lệ khi nó thấp hơn giới hạn dưới của bộ sưu tập hoặc lớn hơn hoặc bằng số lượng phần tử mà nó chứa.
Khi nó được ném
Cho một mảng được khai báo là:
byte[] array = new byte[4];
Bạn có thể truy cập mảng này từ 0 đến 3, các giá trị ngoài phạm vi này sẽ IndexOutOfRangeException
bị ném. Hãy nhớ điều này khi bạn tạo và truy cập vào một mảng.
Độ dài mảng
Trong C #, thông thường, các mảng dựa trên 0. Điều đó có nghĩa là phần tử đầu tiên có chỉ số 0 và phần tử cuối cùng có chỉ mục Length - 1
(trong đó Length
tổng số mục trong mảng) nên mã này không hoạt động:
array[array.Length] = 0;
Ngoài ra, xin lưu ý rằng nếu bạn có một mảng nhiều chiều thì bạn không thể sử dụng Array.Length
cho cả hai chiều, bạn phải sử dụng Array.GetLength()
:
int[,] data = new int[10, 5];
for (int i=0; i < data.GetLength(0); ++i) {
for (int j=0; j < data.GetLength(1); ++j) {
data[i, j] = 1;
}
}
Giới hạn trên không được bao gồm
Trong ví dụ sau, chúng tôi tạo ra một mảng hai chiều thô của Color
. Mỗi mục đại diện cho một pixel, các chỉ số là từ (0, 0)
đến (imageWidth - 1, imageHeight - 1)
.
Color[,] pixels = new Color[imageWidth, imageHeight];
for (int x = 0; x <= imageWidth; ++x) {
for (int y = 0; y <= imageHeight; ++y) {
pixels[x, y] = backgroundColor;
}
}
Mã này sau đó sẽ thất bại vì mảng dựa trên 0 và pixel cuối cùng (dưới cùng bên phải) trong hình ảnh là pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
Trong một kịch bản khác, bạn có thể nhận được ArgumentOutOfRangeException
mã này (ví dụ: nếu bạn đang sử dụng GetPixel
phương thức trên một Bitmap
lớp).
Mảng không tăng
Một mảng là nhanh. Rất nhanh trong tìm kiếm tuyến tính so với mọi bộ sưu tập khác. Đó là do các mục nằm liền kề trong bộ nhớ nên có thể tính địa chỉ bộ nhớ (và gia tăng chỉ là một bổ sung). Không cần phải theo một danh sách nút, toán đơn giản! Bạn trả tiền này với một giới hạn: chúng không thể phát triển, nếu bạn cần nhiều yếu tố hơn, bạn cần phân bổ lại mảng đó (điều này có thể mất một thời gian tương đối dài nếu các mục cũ phải được sao chép sang một khối mới). Bạn thay đổi kích thước chúng vớiArray.Resize<T>()
, ví dụ này thêm một mục mới vào một mảng hiện có:
Array.Resize(ref array, array.Length + 1);
Đừng quên rằng các chỉ số hợp lệ là từ 0
đến Length - 1
. Nếu bạn chỉ đơn giản là cố gắng chỉ định một mục tại Length
bạn sẽ nhận đượcIndexOutOfRangeException
(hành vi này có thể làm bạn bối rối nếu bạn nghĩ rằng chúng có thể tăng theo cú pháp tương tự như Insert
phương pháp của các bộ sưu tập khác).
Mảng đặc biệt với giới hạn dưới tùy chỉnh
Mục đầu tiên trong mảng luôn có chỉ số 0 . Điều này không phải lúc nào cũng đúng bởi vì bạn có thể tạo một mảng với giới hạn dưới tùy chỉnh:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
Trong ví dụ đó, các chỉ số mảng có giá trị từ 1 đến 4. Tất nhiên, giới hạn trên không thể thay đổi.
Đối số sai
Nếu bạn truy cập vào một mảng bằng các đối số không được xác thực (từ đầu vào của người dùng hoặc từ người dùng chức năng), bạn có thể gặp lỗi này:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
Kết quả không mong đợi
Ngoại lệ này cũng có thể bị ném vì một lý do khác: theo quy ước, nhiều hàm tìm kiếm sẽ trả về -1 (nullables đã được giới thiệu với .NET 2.0 và dù sao đó cũng là một quy ước nổi tiếng được sử dụng từ nhiều năm) nếu chúng không ' t tìm thấy bất cứ điều gì Hãy tưởng tượng bạn có một mảng các đối tượng có thể so sánh với một chuỗi. Bạn có thể nghĩ để viết mã này:
// Items comparable with a string
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
myArray[Array.IndexOf(myArray, "Debug")]);
// Arbitrary objects
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
myArray[Array.FindIndex(myArray, x => x.Type == "Debug")]);
Điều này sẽ thất bại nếu không có mục nào myArray
thỏa mãn điều kiện tìm kiếm vì Array.IndexOf()
sẽ trả về -1 và sau đó truy cập mảng sẽ ném.
Ví dụ tiếp theo là một ví dụ ngây thơ để tính toán số lần xuất hiện của một tập hợp số đã cho (biết số tối đa và trả về một mảng trong đó mục tại chỉ số 0 đại diện cho số 0, các mục tại chỉ mục 1 đại diện cho số 1, v.v.):
static int[] CountOccurences(int maximum, IEnumerable<int> numbers) {
int[] result = new int[maximum + 1]; // Includes 0
foreach (int number in numbers)
++result[number];
return result;
}
Tất nhiên, đó là một triển khai khá khủng khiếp nhưng điều tôi muốn thể hiện là nó sẽ thất bại đối với các số và số âm ở trên maximum
.
Làm thế nào nó áp dụng cho List<T>
?
Các trường hợp tương tự như mảng - phạm vi chỉ mục hợp lệ - 0 ( List
chỉ mục của luôn bắt đầu bằng 0) đếnlist.Count
- các phần tử truy cập bên ngoài phạm vi này sẽ gây ra ngoại lệ.
Lưu ý rằng List<T>
ném ArgumentOutOfRangeException
cho các trường hợp tương tự trong đó sử dụng mảngIndexOutOfRangeException
.
Không giống như mảng, List<T>
bắt đầu trống - vì vậy cố gắng truy cập các mục của danh sách vừa tạo dẫn đến ngoại lệ này.
var list = new List<int>();
Trường hợp phổ biến là điền danh sách với lập chỉ mục (tương tự Dictionary<int, T>
) sẽ gây ra ngoại lệ:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader và Cột
Hãy tưởng tượng bạn đang cố đọc dữ liệu từ cơ sở dữ liệu với mã này:
using (var connection = CreateConnection()) {
using (var command = connection.CreateCommand()) {
command.CommandText = "SELECT MyColumn1, MyColumn2 FROM MyTable";
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
ProcessData(reader.GetString(2)); // Throws!
}
}
}
}
GetString()
sẽ ném IndexOutOfRangeException
vì tập dữ liệu của bạn chỉ có hai cột nhưng bạn đang cố lấy giá trị từ cột thứ 3 (chỉ số là luôn dựa trên 0).
Xin lưu ý rằng hành vi này được chia sẻ với hầu hết các IDataReader
triển khai ( SqlDataReader
,OleDbDataReader
và vân vân).
Bạn cũng có thể có ngoại lệ tương tự nếu bạn sử dụng quá tải IDataReader của toán tử lập chỉ mục lấy tên cột và chuyển tên cột không hợp lệ.
Ví dụ, giả sử bạn đã truy xuất một cột có tên là Cột1 nhưng sau đó bạn cố gắng truy xuất giá trị của trường đó bằng
var data = dr["Colum1"]; // Missing the n in Column1.
Điều này xảy ra bởi vì toán tử chỉ mục được triển khai khi cố gắng truy xuất chỉ mục của trường Colum1 không tồn tại. Phương thức GetOrdinal sẽ đưa ra ngoại lệ này khi mã trình trợ giúp nội bộ của nó trả về -1 là chỉ số của "Colum1".
Các
trường hợp khác Có một trường hợp (được ghi lại) khi ngoại lệ này được ném: nếu, trong DataView
, tên cột dữ liệu được cung cấp cho thuộc DataViewSort
tính không hợp lệ.
Làm sao để tránh
Trong ví dụ này, để tôi giả sử, để đơn giản, các mảng luôn luôn là đơn hướng và dựa trên 0. Nếu bạn muốn nghiêm ngặt (hoặc bạn đang phát triển một thư viện), bạn có thể cần phải thay thế 0
bằng GetLowerBound(0)
và .Length
bằng GetUpperBound(0)
(tất nhiên nếu bạn có các tham số của loại System.Arra
y, thì nó không áp dụng cho T[]
). Xin lưu ý rằng trong trường hợp này, giới hạn trên được bao gồm thì mã này:
for (int i=0; i < array.Length; ++i) { }
Nên viết lại như thế này:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Xin lưu ý rằng điều này không được phép (nó sẽ ném InvalidCastException
), đó là lý do tại sao nếu các tham số của T[]
bạn an toàn về các mảng ràng buộc thấp hơn tùy chỉnh:
void foo<T>(T[] array) { }
void test() {
// This will throw InvalidCastException, cannot convert Int32[] to Int32[*]
foo((int)Array.CreateInstance(typeof(int), new int[] { 1 }, new int[] { 1 }));
}
Xác thực tham số
Nếu chỉ mục xuất phát từ một tham số, bạn phải luôn xác thực chúng (ném phù hợp ArgumentException
hoặc ArgumentOutOfRangeException
). Trong ví dụ tiếp theo, các tham số sai có thể gây ra IndexOutOfRangeException
, người dùng chức năng này có thể mong đợi điều này bởi vì họ đang truyền một mảng nhưng không phải lúc nào cũng quá rõ ràng. Tôi đề nghị luôn xác thực các tham số cho các chức năng công cộng:
static void SetRange<T>(T[] array, int from, int length, Func<i, T> function)
{
if (from < 0 || from>= array.Length)
throw new ArgumentOutOfRangeException("from");
if (length < 0)
throw new ArgumentOutOfRangeException("length");
if (from + length > array.Length)
throw new ArgumentException("...");
for (int i=from; i < from + length; ++i)
array[i] = function(i);
}
Nếu chức năng là riêng tư, bạn có thể chỉ cần thay thế if
logic bằng Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Kiểm tra
chỉ mục Mảng trạng thái có thể không đến trực tiếp từ một tham số. Nó có thể là một phần của trạng thái đối tượng. Nói chung luôn luôn là một thực tiễn tốt để xác nhận trạng thái đối tượng (bằng chính nó và với các tham số chức năng, nếu cần). Bạn có thể sử dụng Debug.Assert()
, đưa ra một ngoại lệ thích hợp (mô tả thêm về vấn đề) hoặc xử lý như trong ví dụ này:
class Table {
public int SelectedIndex { get; set; }
public Row[] Rows { get; set; }
public Row SelectedRow {
get {
if (Rows == null)
throw new InvalidOperationException("...");
// No or wrong selection, here we just return null for
// this case (it may be the reason we use this property
// instead of direct access)
if (SelectedIndex < 0 || SelectedIndex >= Rows.Length)
return null;
return Rows[SelectedIndex];
}
}
Xác thực giá trị trả về
Trong một trong các ví dụ trước, chúng tôi trực tiếp sử dụng Array.IndexOf()
giá trị trả về. Nếu chúng ta biết nó có thể thất bại thì tốt hơn là xử lý trường hợp đó:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Cách gỡ lỗi
Theo tôi, hầu hết các câu hỏi, ở đây trên SO, về lỗi này có thể tránh được. Thời gian bạn dành để viết một câu hỏi thích hợp (với một ví dụ làm việc nhỏ và một lời giải thích nhỏ) có thể dễ dàng hơn nhiều so với thời gian bạn sẽ cần gỡ lỗi mã của mình. Trước hết, hãy đọc bài đăng trên blog của Eric Lippert về việc gỡ lỗi các chương trình nhỏ , tôi sẽ không lặp lại những lời của anh ấy ở đây nhưng nó hoàn toàn phải đọc .
Bạn có mã nguồn, bạn có thông báo ngoại lệ với dấu vết ngăn xếp. Đến đó, chọn đúng số dòng và bạn sẽ thấy:
array[index] = newValue;
Bạn đã tìm thấy lỗi của bạn, kiểm tra làm thế nào index
tăng. Đúng không? Kiểm tra mảng được phân bổ như thế nào, có phù hợp với mức index
tăng không? Có đúng theo thông số kỹ thuật của bạn? Nếu bạn trả lời có cho tất cả những câu hỏi này, thì bạn sẽ tìm thấy sự trợ giúp tốt ở đây trên StackOverflow nhưng trước tiên hãy tự mình kiểm tra điều đó. Bạn sẽ tiết kiệm thời gian của riêng bạn!
Một điểm khởi đầu tốt là luôn luôn sử dụng các xác nhận và xác nhận đầu vào. Bạn thậm chí có thể muốn sử dụng hợp đồng mã. Khi có sự cố xảy ra và bạn không thể hiểu điều gì xảy ra với một cái nhìn nhanh về mã của mình thì bạn phải nhờ đến một người bạn cũ: trình gỡ lỗi . Chỉ cần chạy ứng dụng của bạn trong gỡ lỗi bên trong Visual Studio (hoặc IDE yêu thích của bạn), bạn sẽ thấy chính xác dòng nào ném ngoại lệ này, mảng nào có liên quan và chỉ mục nào bạn đang cố gắng sử dụng. Thực sự, 99% số lần bạn sẽ tự giải quyết trong vài phút.
Nếu điều này xảy ra trong sản xuất thì tốt hơn bạn nên thêm các xác nhận vào mã bị buộc tội, có lẽ chúng tôi sẽ không nhìn thấy trong mã của bạn những gì bạn không thể tự nhìn thấy (nhưng bạn luôn có thể đặt cược).
Phía VB.NET của câu chuyện
Tất cả những gì chúng tôi đã nói trong câu trả lời C # đều hợp lệ đối với VB.NET với sự khác biệt về cú pháp rõ ràng nhưng có một điểm quan trọng cần xem xét khi bạn xử lý các mảng VB.NET.
Trong VB.NET, các mảng được khai báo thiết lập giá trị chỉ mục hợp lệ tối đa cho mảng. Nó không phải là số lượng các yếu tố mà chúng tôi muốn lưu trữ trong mảng.
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
Vì vậy, vòng lặp này sẽ lấp đầy mảng với 5 số nguyên mà không gây ra bất kỳ IndexOutOfRangeException
For i As Integer = 0 To 4
myArray(i) = i
Next
Quy tắc VB.NET
Ngoại lệ này có nghĩa là bạn đang cố gắng truy cập một mục bộ sưu tập theo chỉ mục, sử dụng chỉ mục không hợp lệ. Một chỉ mục không hợp lệ khi nó thấp hơn giới hạn dưới của bộ sưu tập hoặc lớn hơnbằng số lượng phần tử mà nó chứa. chỉ số tối đa được phép xác định trong khai báo mảng