عد العناصر من IEnumerable<T> دون التكرار؟
-
05-07-2019 - |
سؤال
private IEnumerable<string> Tables
{
get
{
yield return "Foo";
yield return "Bar";
}
}
لنفترض أنني أريد تكرار هذه الأشياء وكتابة شيء مثل معالجة #n من #m.
هل هناك طريقة يمكنني من خلالها معرفة قيمة m دون التكرار قبل التكرار الرئيسي؟
آمل أن أكون قد أوضحت نفسي.
المحلول
وIEnumerable
لا يدعم هذا. وهذا حسب التصميم. يستخدم IEnumerable
تقييم كسول للحصول على العناصر التي طلب قبل كنت في حاجة إليها.
إذا كنت تريد أن تعرف عدد العناصر دون بالتكرار عليها يمكنك استخدام ICollection<T>
، فإنه له خاصية Count
.
نصائح أخرى
وطريقة تمديد System.Linq.Enumerable.Count
على IEnumerable<T>
لديه التنفيذ التالية:
ICollection<T> c = source as ICollection<TSource>;
if (c != null)
return c.Count;
int result = 0;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
result++;
}
return result;
لذلك يحاول أن يلقي إلى ICollection<T>
، الذي له خاصية Count
، ويستخدم هذا إن أمكن. وإلا فإنه بالتكرار.
وهكذا أفضل رهان هو استخدام طريقة تمديد Count()
على الكائن IEnumerable<T>
الخاص بك، كما أنك سوف تحصل على أفضل أداء ممكن بهذه الطريقة.
وفقط مضيفا اضافية بعض المعلومات:
وتمديد Count()
لا أعاد دائما. النظر في LINQ إلى SQL، حيث يذهب العد إلى قاعدة البيانات، ولكن بدلا من اعادة كافة الصفوف، فإنه يصدر الأمر Count()
SQL والعوائد التي تؤدي بدلا من ذلك.
وبالإضافة إلى ذلك، المترجم (أو وقت التشغيل) ذكي بما فيه الكفاية أنه سيدعو الكائنات Count()
طريقة إذا كان لديه واحد. لذلك فمن <م> لا م> كما يقول المستجيبين أخرى، ويجري جاهل تماما وبالتكرار دائما من أجل الاعتماد العناصر.
في حالات كثيرة لا يكون مبرمج هو مجرد التحقق if( enumerable.Count != 0 )
باستخدام طريقة تمديد Any()
، كما هو الحال في if( enumerable.Any() )
هو أكثر كفاءة مع تقييم كسول LINQ كما أنه يمكن قصيرة الدائرة مرة واحدة فإنه يمكن تحديد وجود أي عناصر. كما انها أكثر قابلية للقراءة
لدى أحد أصدقائي سلسلة من منشورات المدونة التي تقدم توضيحًا لسبب عدم قدرتك على القيام بذلك.يقوم بإنشاء دالة تُرجع IEnumerable حيث يُرجع كل تكرار الرقم الأولي التالي، على طول الطريق ulong.MaxValue
, ، ولا يتم احتساب العنصر التالي حتى تطلبه.سؤال سريع ومفاجئ:كم عدد العناصر التي تم إرجاعها؟
إليكم المشاركات، لكنها طويلة نوعاً ما:
- ما وراء الحلقات (يوفر فئة EnumerableUtility الأولية المستخدمة في المشاركات الأخرى)
- تطبيقات التكرار (التنفيذ الأولي)
- طرق التمديد المجنونة:ToLazyList (تحسينات الأداء)
وIEnumerable لا يمكن الاعتماد دون بالتكرار.
وتحت ظروف "طبيعية"، سيكون من الممكن لفئات تنفيذ IEnumerable أو IEnumerable
وبدلا من ذلك، عد ذلك هو أسلوب الإرشاد، والمعرفة على فئة ثابتة Enumerable. هذا يعني أنه يمكن أن يطلق على أي مثيل فئة مشتقة IEnumerable
وبدلا من ذلك يمكنك القيام بما يلي:
Tables.ToList<string>().Count;
لا، ليس بشكل عام. نقطة واحدة في استخدام enumerables هي أن مجموعة الفعلي من الكائنات في التعداد غير معروف (مقدما، أو حتى على الإطلاق).
ويمكنك استخدام System.Linq.
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private IEnumerable<string> Tables
{
get {
yield return "Foo";
yield return "Bar";
}
}
static void Main()
{
var x = new Test();
Console.WriteLine(x.Tables.Count());
}
}
وستحصل على نتيجة "2".
والذهاب أبعد من السؤال المباشر الخاص بك (والتي تم الإجابة بدقة في السلبية)، إذا كنت تبحث الإبلاغ عن التقدم المحرز في حين تجهيز وenumerable، قد ترغب في النظر في بلدي بلوق وظيفة <لأ href = "HTTP: // blog.functionalfun.net/2008/07/reporting-progress-during-linq-queries.html "يختلط =" noreferrer "> الإبلاغ عن التقدم المحرز خلال ينق استعلامات .
وهذا يتيح لك القيام بذلك:
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (sender, e) =>
{
// pretend we have a collection of
// items to process
var items = 1.To(1000);
items
.WithProgressReporting(progress => worker.ReportProgress(progress))
.ForEach(item => Thread.Sleep(10)); // simulate some real work
};
ولقد استخدمت هذه الطريقة داخل وسيلة للتحقق من مر في محتوى IEnumberable
if( iEnum.Cast<Object>().Count() > 0)
{
}
وداخل طريقة مثل هذا:
GetDataTable(IEnumberable iEnum)
{
if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further
}
وهذا يعتمد على إصدار صافي وتنفيذ الكائن IEnumerable الخاص بك.
مايكروسوفت قد حددت طريقة IEnumerable.Count للتحقق من تنفيذ، ويستخدم ICollection.Count أو ICollection
وأدناه هو MSIL من ILDASM لSystem.Core، الذي يقيم System.Linq.
.method public hidebysig static int32 Count<TSource>(class
[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source) cil managed
{
.custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
// Code size 85 (0x55)
.maxstack 2
.locals init (class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource> V_0,
class [mscorlib]System.Collections.ICollection V_1,
int32 V_2,
class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> V_3)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldstr "source"
IL_0008: call class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string)
IL_000d: throw
IL_000e: ldarg.0
IL_000f: isinst class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>
IL_0014: stloc.0
IL_0015: ldloc.0
IL_0016: brfalse.s IL_001f
IL_0018: ldloc.0
IL_0019: callvirt instance int32 class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>::get_Count()
IL_001e: ret
IL_001f: ldarg.0
IL_0020: isinst [mscorlib]System.Collections.ICollection
IL_0025: stloc.1
IL_0026: ldloc.1
IL_0027: brfalse.s IL_0030
IL_0029: ldloc.1
IL_002a: callvirt instance int32 [mscorlib]System.Collections.ICollection::get_Count()
IL_002f: ret
IL_0030: ldc.i4.0
IL_0031: stloc.2
IL_0032: ldarg.0
IL_0033: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
IL_0038: stloc.3
.try
{
IL_0039: br.s IL_003f
IL_003b: ldloc.2
IL_003c: ldc.i4.1
IL_003d: add.ovf
IL_003e: stloc.2
IL_003f: ldloc.3
IL_0040: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0045: brtrue.s IL_003b
IL_0047: leave.s IL_0053
} // end .try
finally
{
IL_0049: ldloc.3
IL_004a: brfalse.s IL_0052
IL_004c: ldloc.3
IL_004d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0052: endfinally
} // end handler
IL_0053: ldloc.2
IL_0054: ret
} // end of method Enumerable::Count
وهنا لمناقشة كبيرة حول <لأ href = "http://blogs.msdn.com/ericwhite/pages/Lazy-Evaluation-_2800_and-in-contrast_2C00_-Eager-Evaluation_2900_.aspx" يختلط = "نوفولو noreferrer" > تقييم كسول و المؤجلة التنفيذ . أساسا لديك لتجسيد القائمة للحصول على تلك القيمة.
والنتيجة من وظيفة IEnumerable.Count () قد تكون خاطئة. هذه عينة بسيطة جدا لاختبار:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
var result = test.Split(7);
int cnt = 0;
foreach (IEnumerable<int> chunk in result)
{
cnt = chunk.Count();
Console.WriteLine(cnt);
}
cnt = result.Count();
Console.WriteLine(cnt);
Console.ReadLine();
}
}
static class LinqExt
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
{
if (chunkLength <= 0)
throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");
IEnumerable<T> result = null;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
result = GetChunk(enumerator, chunkLength);
yield return result;
}
}
}
static IEnumerable<T> GetChunk<T>(IEnumerator<T> source, int chunkLength)
{
int x = chunkLength;
do
yield return source.Current;
while (--x > 0 && source.MoveNext());
}
}
}
ويجب أن يكون نتيجة (7،7،3،3) ولكن النتيجة الفعلية هي (7،7،3،17)
والطريقة الوحيدة لديك حساب سريع هو عندما جمع الأصلي له مفهرس (مثل مجموعة). من أجل خلق رمز عام مع الحد الأدنى من المتطلبات يمكنك استخدام IEnumerable ولكن إذا كنت في حاجة إلى العد أيضا ثم طريقي مفضلة لاستخدام هذه الواجهة:
public interface IEnumAndCount<out T> : IEnumerable<T>
{
int Count { get; }
}
إذا لم يكن لديك مجموعة الأصلي أي مفهرس، يمكن تنفيذ عدد بك أعاد خلال جمع، مع ضرب المعروفة في O الأداء (ن).
إذا كنت لا تريد استخدام شيء مشابه لIEnumAndCount، وأفضل رهان هو أن يذهب مع Linq.Count عن الأسباب التي دانيال Earwicker بالقرب من أعلى هذه المسألة.
وحظا سعيدا!
لا.
هل ترى أن المعلومات المتاحة في أي مكان في رمز كنت قد كتبت؟
هل يمكن ان نقول ان المجمع يمكن "رؤية" أن هناك اثنين فقط، ولكن هذا يعني أنه سيحتاج إلى تحليل كل أسلوب مكرر تبحث فقط لهذه الحالة المرضية الخاصة. وحتى لو فعلت، كيف تقرأ ذلك، نظرا للحدود من IEnumerable؟
وأود أن أقترح الدعوة ToList. نعم تقومون به التعداد في وقت مبكر، ولكن لا يزال لديك الوصول إلى قائمة العناصر.
قد لا تسفر عن أفضل أداء، ولكن يمكنك استخدام LINQ لحساب العناصر في IEnumerable:
public int GetEnumerableCount(IEnumerable Enumerable)
{
return (from object Item in Enumerable
select Item).Count();
}
وأنا استخدم IEnum<string>.ToArray<string>().Length
وأنه يعمل بشكل جيد.
وأنا استخدم هذا الرمز، إذا كان لدي قائمة سلاسل:
((IList<string>)Table).Count