คุณจะดูคอลเลกชันใน LINQ ได้อย่างไรว่าคุณมี a startIndexและ a count?
คุณจะดูคอลเลกชันใน LINQ ได้อย่างไรว่าคุณมี a startIndexและ a count?
คำตอบ:
ไม่กี่เดือนก่อนหน้านี้ฉันได้เขียนบล็อกโพสต์เกี่ยวกับ Fluent Interfaces และ LINQ ซึ่งใช้วิธีการขยายIQueryable<T>และอีกชั้นหนึ่งเพื่อให้วิธีการแบ่งหน้าคอลเลกชัน LINQ ตามธรรมชาติดังต่อไปนี้
var query = from i in ideas
select i;
var pagedCollection = query.InPagesOf(10);
var pageOfIdeas = pagedCollection.Page(2);
คุณจะได้รับรหัสจากเก็บรหัส MSDN หน้า: ท่อกรอง API คล่องแคล่วและ LINQ กับ SQL
มันง่ายมากด้วยวิธีการSkipและการTakeขยาย
var query = from i in ideas
select i;
var paggedCollection = query.Skip(startIndex).Take(count);
ฉันแก้ไขสิ่งนี้ให้แตกต่างจากที่คนอื่นมีอยู่เล็กน้อยเพราะฉันต้องสร้างเลขหน้าของตัวเองด้วยทวน ดังนั้นฉันจึงรวบรวมหมายเลขหน้าสำหรับคอลเลกชันรายการที่ฉันมีก่อน
// assumes that the item collection is "myItems"
int pageCount = (myItems.Count + PageSize - 1) / PageSize;
IEnumerable<int> pageRange = Enumerable.Range(1, pageCount);
// pageRange contains [1, 2, ... , pageCount]
การใช้สิ่งนี้ฉันสามารถแบ่งพาร์ติชันคอลเลคชันไอเท็มเป็นคอลเลคชัน "เพจ" ได้อย่างง่ายดาย หน้าในกรณีนี้เป็นเพียงชุดของรายการ ( IEnumerable<Item>) นี่คือวิธีที่คุณสามารถทำได้โดยใช้SkipและTakeร่วมกับการเลือกดัชนีจากที่pageRangeสร้างไว้ด้านบน:
IEnumerable<IEnumerable<Item>> pageRange
.Select((page, index) =>
myItems
.Skip(index*PageSize)
.Take(PageSize));
แน่นอนว่าคุณต้องจัดการแต่ละหน้าเป็นคอลเลกชั่นเพิ่มเติม แต่เช่นถ้าคุณกำลังทำรังซ้ำแล้วซ้ำอีกนี่เป็นเรื่องง่ายที่จะจัดการ
หนึ่งซับ TLDRรุ่นจะเป็นนี้:
var pages = Enumerable
.Range(0, pageCount)
.Select((index) => myItems.Skip(index*PageSize).Take(PageSize));
ซึ่งสามารถใช้ได้ดังนี้:
for (Enumerable<Item> page : pages)
{
// handle page
for (Item item : page)
{
// handle item in page
}
}
คำถามนี้ค่อนข้างเก่า แต่ฉันต้องการโพสต์อัลกอริธึมการเพจของฉันที่แสดงขั้นตอนทั้งหมด (รวมถึงการโต้ตอบกับผู้ใช้)
const int pageSize = 10;
const int count = 100;
const int startIndex = 20;
int took = 0;
bool getNextPage;
var page = ideas.Skip(startIndex);
do
{
Console.WriteLine("Page {0}:", (took / pageSize) + 1);
foreach (var idea in page.Take(pageSize))
{
Console.WriteLine(idea);
}
took += pageSize;
if (took < count)
{
Console.WriteLine("Next page (y/n)?");
char answer = Console.ReadLine().FirstOrDefault();
getNextPage = default(char) != answer && 'y' == char.ToLowerInvariant(answer);
if (getNextPage)
{
page = page.Skip(pageSize);
}
}
}
while (getNextPage && took < count);
อย่างไรก็ตามหากคุณอยู่ในผลการดำเนินงานและในโค้ดการผลิตเราทุกคนมีประสิทธิภาพคุณไม่ควรใช้การเพจของ LINQ ดังที่แสดงไว้ด้านบน แต่ควรเป็นพื้นฐานIEnumeratorในการใช้เพจด้วยตัวคุณเอง ตามความเป็นจริงมันง่ายเหมือนขั้นตอนวิธี LINQ ที่แสดงด้านบน แต่มีประสิทธิภาพมากกว่า:
const int pageSize = 10;
const int count = 100;
const int startIndex = 20;
int took = 0;
bool getNextPage = true;
using (var page = ideas.Skip(startIndex).GetEnumerator())
{
do
{
Console.WriteLine("Page {0}:", (took / pageSize) + 1);
int currentPageItemNo = 0;
while (currentPageItemNo++ < pageSize && page.MoveNext())
{
var idea = page.Current;
Console.WriteLine(idea);
}
took += pageSize;
if (took < count)
{
Console.WriteLine("Next page (y/n)?");
char answer = Console.ReadLine().FirstOrDefault();
getNextPage = default(char) != answer && 'y' == char.ToLowerInvariant(answer);
}
}
while (getNextPage && took < count);
}
คำอธิบาย: ข้อเสียของการใช้Skip()หลาย ๆ ครั้งในลักษณะ "เรียงซ้อน" คือจะไม่เก็บ "ตัวชี้" ของการวนซ้ำโดยที่มันถูกข้ามครั้งสุดท้าย - แทนที่ลำดับเดิมจะถูกโหลดไว้ด้านหน้าด้วยการข้ามการโทรซึ่งจะนำไปสู่การ "บริโภค" เพจที่ "ใช้ไปแล้ว" ซ้ำแล้วซ้ำเล่า - คุณสามารถพิสูจน์ตัวเองได้เมื่อคุณสร้างลำดับideasเพื่อให้เกิดผลข้างเคียง -> แม้ว่าคุณจะข้าม 10-20 และ 20-30 ไปแล้วและต้องการประมวลผล 40+ คุณจะเห็นผลข้างเคียงทั้งหมดของ 10-30 ถูกดำเนินการอีกครั้งก่อนที่คุณจะเริ่มทำซ้ำ 40+ ตัวแปรที่ใช้IEnumerableอินเทอร์เฟซโดยตรงจะจำตำแหน่งของส่วนท้ายของหน้าตรรกะสุดท้ายแทนดังนั้นจึงไม่จำเป็นต้องข้ามอย่างชัดเจนและจะไม่เกิดผลข้างเคียงซ้ำ