มันคืออะไร?
ข้อยกเว้นนี้หมายความว่าคุณกำลังพยายามเข้าถึงรายการคอลเลกชันตามดัชนีโดยใช้ดัชนีที่ไม่ถูกต้อง ดัชนีไม่ถูกต้องเมื่อต่ำกว่าขอบเขตล่างของคอลเลกชันหรือมากกว่าหรือเท่ากับจำนวนองค์ประกอบที่มี
เมื่อมันถูกโยนทิ้ง
รับอาร์เรย์ประกาศเป็น:
byte[] array = new byte[4];
คุณสามารถเข้าถึงอาร์เรย์นี้ได้ตั้งแต่ 0 ถึง 3 ค่าที่อยู่นอกช่วงนี้จะทำให้เกิดIndexOutOfRangeException
การโยน จดจำสิ่งนี้เมื่อคุณสร้างและเข้าถึงอาร์เรย์
Array Length
ใน C # โดยปกติอาร์เรย์จะเป็น 0 หมายความว่าองค์ประกอบแรกมีดัชนี 0 และองค์ประกอบสุดท้ายมีดัชนีLength - 1
(ซึ่งLength
เป็นจำนวนรายการทั้งหมดในอาร์เรย์) ดังนั้นรหัสนี้จึงใช้งานไม่ได้:
array[array.Length] = 0;
ยิ่งไปกว่านั้นโปรดทราบว่าหากคุณมีหลายมิติอาเรย์แล้วคุณไม่สามารถใช้Array.Length
สำหรับทั้งสองมิติคุณต้องใช้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;
}
}
ที่ถูกผูกไว้บนไม่รวม
ในตัวอย่างต่อไปนี้เราสร้างอาร์เรย์ bidimensional Color
ดิบ แต่ละรายการจะแสดงให้เห็นถึงพิกเซล, ดัชนีจากไป(0, 0)
(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;
}
}
รหัสนี้จะล้มเหลวเนื่องจากอาร์เรย์เป็น 0 และพิกเซลสุดท้าย (ล่างขวา) ในรูปภาพคือpixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
ในสถานการณ์อื่นคุณอาจได้รับArgumentOutOfRangeException
รหัสนี้ (ตัวอย่างเช่นหากคุณใช้GetPixel
วิธีการในBitmap
ชั้นเรียน)
อาร์เรย์ไม่เติบโต
อาร์เรย์มีความรวดเร็ว การค้นหาแบบเส้นตรงรวดเร็วมากเมื่อเทียบกับคอลเล็กชันอื่น ๆ มันเป็นเพราะรายการที่อยู่ติดกันในหน่วยความจำเพื่อให้สามารถคำนวณที่อยู่หน่วยความจำ (และเพิ่มเป็นเพียงการเพิ่ม) ไม่จำเป็นต้องติดตามรายการโหนดคณิตศาสตร์อย่างง่าย! คุณจ่ายสิ่งนี้โดยมีข้อ จำกัด : พวกมันไม่สามารถเติบโตได้ถ้าคุณต้องการองค์ประกอบเพิ่มเติมที่คุณต้องการจัดสรรอาร์เรย์ใหม่ (ซึ่งอาจใช้เวลาค่อนข้างนานถ้าต้องคัดลอกรายการเก่าไปยังบล็อกใหม่) คุณปรับขนาดพวกเขาด้วยArray.Resize<T>()
ตัวอย่างนี้เพิ่มรายการใหม่ไปยังอาร์เรย์ที่มีอยู่:
Array.Resize(ref array, array.Length + 1);
อย่าลืมว่าดัชนีที่ถูกต้องจากไป0
Length - 1
หากคุณเพียงแค่พยายามกำหนดรายการที่Length
คุณจะได้รับIndexOutOfRangeException
(พฤติกรรมนี้อาจทำให้คุณสับสนหากคุณคิดว่าพวกเขาอาจเพิ่มขึ้นด้วยไวยากรณ์ที่คล้ายกับInsert
วิธีการของคอลเลกชันอื่น ๆ )
อาร์เรย์พิเศษที่มีขอบเขตล่างที่กำหนดเอง
รายการแรกในอาร์เรย์มีดัชนี 0เสมอ สิ่งนี้ไม่เป็นความจริงเสมอเพราะคุณสามารถสร้างอาร์เรย์ที่มีขอบเขตล่างที่กำหนดเองได้:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
ในตัวอย่างนั้นดัชนีอาร์เรย์มีค่าตั้งแต่ 1 ถึง 4 แน่นอนว่าขอบเขตบนไม่สามารถเปลี่ยนแปลงได้
อาร์กิวเมนต์ไม่ถูกต้อง
หากคุณเข้าถึงอาร์เรย์โดยใช้อาร์กิวเมนต์ที่ไม่ผ่านการอนุมัติ (จากอินพุตของผู้ใช้หรือจากผู้ใช้ฟังก์ชัน) คุณอาจได้รับข้อผิดพลาดนี้:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
ผลลัพธ์ที่ไม่คาดคิด
ข้อยกเว้นนี้อาจถูกโยนด้วยเหตุผลอื่นเช่นกัน: โดยการประชุมฟังก์ชั่นการค้นหาจำนวนมากจะส่งกลับ -1 (nullable ได้รับการแนะนำกับ. NET 2.0 และต่อไปก็เป็นแบบแผนที่รู้จักกันดีในการใช้งานจากหลายปี) ไม่พบอะไรเลย ลองจินตนาการว่าคุณมีอาร์เรย์ของวัตถุที่เปรียบเทียบได้กับสตริง คุณอาจคิดว่าจะเขียนรหัสนี้:
// 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")]);
สิ่งนี้จะล้มเหลวหากไม่มีรายการใดในmyArray
จะตอบสนองเงื่อนไขการค้นหาเพราะArray.IndexOf()
จะคืนค่า -1 จากนั้นการเข้าถึงอาร์เรย์จะโยน
ตัวอย่างถัดไปเป็นตัวอย่างที่ไร้เดียงสาในการคำนวณการเกิดขึ้นของชุดตัวเลขที่กำหนด (รู้จำนวนสูงสุดและส่งกลับอาร์เรย์โดยที่รายการที่ดัชนี 0 แทนหมายเลข 0 รายการที่ดัชนี 1 หมายถึงหมายเลข 1 และอื่น ๆ ):
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;
}
แน่นอนว่ามันเป็นเรื่องการดำเนินงานที่น่ากลัวสวย maximum
แต่สิ่งที่ฉันต้องการที่จะแสดงให้เห็นก็คือว่ามันจะล้มเหลวสำหรับตัวเลขที่ติดลบและตัวเลขดังกล่าวข้างต้น
มันใช้ได้กับList<T>
อย่างไร?
กรณีเช่นเดียวกับช่วงอาร์เรย์ของดัชนีที่ถูกต้อง - 0 ( List
ดัชนีของเสมอเริ่มต้นด้วย 0) list.Count
องค์ประกอบการเข้าถึงนอกช่วงนี้จะทำให้เกิดข้อยกเว้น
โปรดทราบว่าList<T>
พ่นสำหรับกรณีเดียวกับที่ใช้อาร์เรย์ArgumentOutOfRangeException
IndexOutOfRangeException
แตกต่างจากอาร์เรย์List<T>
เริ่มว่างเปล่า - ดังนั้นพยายามเข้าถึงรายการของรายการที่เพิ่งสร้างนำไปสู่ข้อยกเว้นนี้
var list = new List<int>();
กรณีทั่วไปคือการเติมรายการที่มีการจัดทำดัชนี (คล้ายกับDictionary<int, T>
) จะทำให้เกิดข้อยกเว้น:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader และคอลัมน์
ลองจินตนาการว่าคุณกำลังพยายามอ่านข้อมูลจากฐานข้อมูลด้วยรหัสนี้:
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()
จะโยนIndexOutOfRangeException
เพราะชุดข้อมูลของคุณมีเพียงสองคอลัมน์ แต่คุณพยายามรับค่าจากคอลัมน์ที่สาม (ดัชนีมักเป็นแบบ0)
โปรดทราบว่าการทำงานนี้จะใช้ร่วมกันกับส่วนใหญ่IDataReader
การใช้งาน ( SqlDataReader
, OleDbDataReader
และอื่น ๆ )
คุณสามารถรับข้อยกเว้นเดียวกันได้เช่นกันหากคุณใช้ IDataReader โอเวอร์โหลดของโอเปอเรเตอร์ตัวทำดัชนีที่ใช้ชื่อคอลัมน์และส่งชื่อคอลัมน์ที่ไม่ถูกต้อง
สมมติว่าคุณได้ดึงคอลัมน์ชื่อColumn1แต่คุณพยายามดึงค่าของฟิลด์นั้นด้วย
var data = dr["Colum1"]; // Missing the n in Column1.
สิ่งนี้เกิดขึ้นเนื่องจากตัวดำเนินการตัวจัดทำดัชนีพยายามดึงดัชนีของเขตข้อมูลColum1ที่ไม่มีอยู่ เมธอด GetOrdinal จะส่งข้อยกเว้นนี้เมื่อโค้ดตัวช่วยภายในของมันคืนค่า -1 เป็นดัชนีของ "Colum1"
อื่น ๆ
มีอีกกรณี (เอกสาร) เมื่อข้อยกเว้นนี้ถูกโยน: ถ้า, ในDataView
, ชื่อคอลัมน์ข้อมูลที่ถูกส่งไปยังDataViewSort
สถานที่ให้บริการไม่ถูกต้อง
วิธีการหลีกเลี่ยง
ในตัวอย่างนี้ขอสมมติว่าสำหรับความเรียบง่ายอาร์เรย์นั้นเป็นแบบ monodimensional และเป็น 0 เสมอ หากคุณต้องการเข้มงวด (หรือคุณกำลังพัฒนาห้องสมุด) คุณอาจต้องแทนที่0
ด้วยGetLowerBound(0)
และ.Length
ด้วยGetUpperBound(0)
(แน่นอนถ้าคุณมีพารามิเตอร์ประเภทSystem.Arra
y จะไม่สามารถใช้ได้T[]
) โปรดทราบว่าในกรณีนี้มีการรวมขอบเขตบนด้วยรหัสนี้:
for (int i=0; i < array.Length; ++i) { }
ควรเขียนใหม่เช่นนี้:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
โปรดทราบว่าสิ่งนี้ไม่ได้รับอนุญาต (มันจะเกิดขึ้นInvalidCastException
) นั่นเป็นสาเหตุว่าถ้าพารามิเตอร์ของT[]
คุณปลอดภัยเกี่ยวกับอาร์เรย์ล่างที่กำหนดเอง:
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 }));
}
ตรวจสอบพารามิเตอร์
ดัชนีถ้ามาจากพารามิเตอร์คุณควรตรวจสอบพวกเขา (ขว้างปาเหมาะสมArgumentException
หรือArgumentOutOfRangeException
) ในตัวอย่างถัดไปอาจทำให้เกิดพารามิเตอร์ที่ไม่ถูกต้องIndexOutOfRangeException
ผู้ใช้ของฟังก์ชันนี้อาจคาดหวังสิ่งนี้เพราะพวกเขาผ่านอาร์เรย์ แต่ก็ไม่ชัดเจนนัก ฉันขอแนะนำให้ตรวจสอบพารามิเตอร์สำหรับฟังก์ชั่นสาธารณะเสมอ:
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);
}
หากฟังก์ชั่นเป็นส่วนตัวคุณสามารถแทนที่if
ตรรกะด้วยDebug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
ตรวจสอบ
ดัชนีสถานะอาร์เรย์วัตถุอาจไม่ได้มาโดยตรงจากพารามิเตอร์ มันอาจเป็นส่วนหนึ่งของสถานะวัตถุ โดยทั่วไปมักเป็นแนวปฏิบัติที่ดีในการตรวจสอบสถานะของวัตถุ (ด้วยตัวเองและด้วยพารามิเตอร์ฟังก์ชันหากจำเป็น) คุณสามารถใช้Debug.Assert()
, โยนข้อยกเว้นที่เหมาะสม (อธิบายเพิ่มเติมเกี่ยวกับปัญหา) หรือจัดการเช่นในตัวอย่างนี้:
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];
}
}
ตรวจสอบค่าส่งคืน
ในหนึ่งในตัวอย่างก่อนหน้านี้เราใช้Array.IndexOf()
ค่าส่งคืนโดยตรง ถ้าเรารู้ว่ามันล้มเหลวก็ควรจัดการกรณีนั้นดีกว่า:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
วิธีแก้จุดบกพร่อง
ในความคิดของฉันคำถามส่วนใหญ่ที่นี่ใน SO เกี่ยวกับข้อผิดพลาดนี้สามารถหลีกเลี่ยงได้เพียง เวลาที่คุณใช้ในการเขียนคำถามที่เหมาะสม (พร้อมตัวอย่างการทำงานขนาดเล็กและคำอธิบายเล็ก ๆ ) อาจมากกว่าเวลาที่คุณจะต้องดีบักโค้ดของคุณ ครั้งแรกของทั้งหมดอ่านโพสต์บล็อกนี้เอริค Lippert เกี่ยวกับการแก้จุดบกพร่องของโปรแกรมขนาดเล็กที่ฉันจะไม่ทำซ้ำคำพูดของเขาที่นี่ แต่ก็อย่างจะต้องอ่าน
คุณมีซอร์สโค้ดคุณมีข้อความข้อยกเว้นที่มีการติดตามสแต็ก ไปที่นั่นเลือกหมายเลขสายที่ถูกต้องแล้วคุณจะเห็น:
array[index] = newValue;
คุณพบข้อผิดพลาดตรวจสอบว่าindex
จะเพิ่มขึ้นอย่างไร ถูกต้องหรือไม่ ตรวจสอบว่ามีการจัดสรรอาเรย์อย่างไรและindex
เพิ่มขึ้นอย่างไร? มันถูกต้องตามข้อกำหนดของคุณ? หากคุณตอบว่าใช่สำหรับคำถามเหล่านี้คุณจะพบความช่วยเหลือที่ดีที่ StackOverflow แต่โปรดตรวจสอบด้วยตัวเองก่อน คุณจะประหยัดเวลาของคุณเอง!
จุดเริ่มต้นที่ดีคือการใช้การยืนยันและการตรวจสอบอินพุต คุณอาจต้องการใช้สัญญารหัส เมื่อเกิดข้อผิดพลาดและคุณไม่สามารถทราบได้ว่าเกิดอะไรขึ้นกับการดูรหัสของคุณอย่างรวดเร็วคุณต้องหันไปหาเพื่อนเก่านั่นคือดีบักเกอร์ เพียงเรียกใช้แอปพลิเคชันของคุณในการดีบักภายใน Visual Studio (หรือ IDE ที่คุณชื่นชอบ) คุณจะเห็นว่าบรรทัดใดที่มีข้อยกเว้นนี้อาร์เรย์ที่เกี่ยวข้องและดัชนีที่คุณกำลังพยายามใช้ แท้จริงแล้ว 99% ของจำนวนครั้งที่คุณจะแก้ไขได้ด้วยตัวเองในเวลาไม่กี่นาที
หากสิ่งนี้เกิดขึ้นในการผลิตคุณควรที่จะเพิ่มการยืนยันในโค้ดที่ถูกกล่าวหาเราอาจจะไม่เห็นรหัสของคุณในสิ่งที่คุณมองไม่เห็นด้วยตัวเอง (แต่คุณสามารถเดิมพันได้เสมอ)
ด้าน VB.NET ของเรื่อง
ทุกสิ่งที่เราได้กล่าวไว้ในคำตอบ C # นั้นถูกต้องสำหรับ VB.NET ที่มีความแตกต่างทางไวยากรณ์ที่ชัดเจน แต่มีประเด็นสำคัญที่ต้องพิจารณาเมื่อคุณจัดการกับอาร์เรย์ VB.NET
ใน VB.NET อาร์เรย์จะประกาศการตั้งค่าค่าดัชนีที่ถูกต้องสูงสุดสำหรับอาร์เรย์ มันไม่นับองค์ประกอบที่เราต้องการเก็บไว้ในอาร์เรย์
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
ดังนั้นลูปนี้จะเติมอาร์เรย์ด้วยจำนวนเต็ม 5 ตัวโดยไม่ทำให้IndexOutOfRangeExceptionใด ๆ
For i As Integer = 0 To 4
myArray(i) = i
Next
กฎ VB.NET
ข้อยกเว้นนี้หมายความว่าคุณกำลังพยายามเข้าถึงรายการคอลเลกชันตามดัชนีโดยใช้ดัชนีที่ไม่ถูกต้อง ดัชนีไม่ถูกต้องเมื่อต่ำกว่าขอบเขตล่างของคอลเลกชันหรือมากกว่าเท่ากับจำนวนองค์ประกอบที่มี ดัชนีสูงสุดที่อนุญาตที่กำหนดไว้ในการประกาศอาร์เรย์