C # foreach iyileştirmeleri?


9

Ben sık sık bir foreach içinde bir döngü sayım indeksi istiyorum ve bir tamsayı oluşturmak, kullanmak, artış, vb yapmak istiyorum programlama sırasında bu koşmak. bir foreach içindeki döngü sayısı? Diğer döngülerde de kullanılabilir.

Bu, derleyicinin bu anahtar kelimenin herhangi bir yerde değil, bir döngü yapısında kullanılmasına nasıl izin vermeyeceğini gören anahtar kelimelerin kullanımına aykırı mı?


7
Bir uzatma yöntemiyle kendiniz yapabilirsiniz, Dan Finch'in cevabına bakın: stackoverflow.com/a/521894/866172 (en iyi not değil, ama bence "daha temiz" olanı)
Jalayn

Perl ve $ _ gibi şeyler akla geliyor. Onlardan nefret ediyorum.
Yam Marcovic

2
C # hakkında bildiğim kadarıyla, içeriğe dayalı birçok anahtar kelime var, bu yüzden bu "anahtar kelimelerin kullanımına karşı gitmek olmaz". Bununla birlikte, aşağıdaki cevapta belirtildiği gibi, muhtemelen sadece bir for () döngü kullanmak istersiniz .
Craige

@Jalayn Lütfen bunu yanıt olarak gönderin. En iyi yaklaşımlardan biridir.
Apoorv Khurasia

@MonsterTruck: Yapması gerektiğini düşünmüyorum. Asıl çözüm, bu soruyu SO'ya taşımak ve Jalayn'ın bağlandığı sorunun bir kopyası olarak kapatmak olacaktır.
Brian

Yanıtlar:


54

Bir döngü içinde bir döngü sayısına ihtiyacınız varsa foreachneden sadece normal bir fordöngü kullanmıyorsunuz ? foreachDöngü belirli kullanımlarını sağlamak amaçlı olduğunu fordaha basit döngüler. Görünüşe göre, sadeliğinin foreachartık yararlı olmadığı bir durum var .


1
+1 - iyi bir nokta. Nesnedeki öğelerin sayısını bulmayla ilgili herhangi bir sorunu önlemek için IEnumerable.Count veya object.length öğesini düzenli bir döngü ile kullanmak yeterince kolaydır.

11
@ GlenH7: Uygulamaya bağlı olarak, IEnumerable.Countpahalı olabilir (veya bir kez sadece numaralandırma ise imkansız). Bunu güvenle yapabilmeniz için temel kaynağın ne olduğunu bilmeniz gerekir.
İkili Worrier

2
-1: Bill Wagner Daha Etkili C # hep sadık neden açıklar foreachüzerinde for (int i = 0…). Birkaç sayfa yazdı, ancak iki kelimeyle özetleyebilirim - yineleme konusunda derleyici optimizasyonu. Tamam bu iki kelime değildi.
Apoorv Khurasia

13
@MonsterTruck: Bill Wagner ne yazdıysa, bir fordöngü daha iyi okunabilir kod veren OP tarafından tanımlanan gibi yeterli durumları gördüm (ve teorik derleyici optimizasyonu genellikle erken optimizasyon).
Doc Brown

1
@DocBrown Bu, erken optimizasyon kadar basit değil. Geçmişte darboğazları kendim belirledim ve bu yaklaşımı kullanarak bunları düzelttim (ancak koleksiyondaki binlerce nesne ile uğraştığımı itiraf edeceğim). Yakında çalışan bir örnek yayınlamayı umuyoruz. Bu arada, burada Jon Skeet . [Performans iyileştirmenin önemli olmayacağını, ancak bunun büyük ölçüde koleksiyona ve nesne sayısına bağlı olduğunu söylüyor].
Apoorv Khurasia

37

Bunun gibi anonim türleri kullanabilirsiniz

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Bu Python'un enumerateişlevine benzer

for i, v in enumerate(items):
    print v, i

+1 Oh ... ampul. Bu yaklaşımı seviyorum. Neden daha önce hiç düşünmedim?
Phil

17
Bunu C # 'da klasik bir döngü için tercih edenler, okunabilirlik hakkında garip bir görüşe sahiptir. Bu düzgün bir hile olabilir ama üretim kalite kodunda böyle bir şeye izin vermezdim. Döngü için bir klasikten çok daha gürültülü ve hızlı olarak anlaşılamıyor.
Falcon

4
@Falcon, bu döngü için eski bir moda KULLANAMIYORSANIZ ( geçerli bir sayıdır) geçerli bir alternatiftir . Bu, etrafta çalışmak zorunda olduğum birkaç nesnenin koleksiyonları ...
Fabricio Araujo

8
@FabricioAraujo: Döngüler için mutlaka C # sayımı gerektirmez. Bildiğim kadarıyla, döngüler için C'lerle aynılar, bu da döngüyü sonlandırmak için herhangi bir boole değerini kullanabileceğiniz anlamına gelir. MoveNext yanlış döndürdüğünde numaralandırmanın sonu için bir kontrol gibi.
Zan Lynx

1
Bu, foreach bloğunun başlangıcındaki artışı bulmak yerine onu işlemek için tüm kodlara sahip olma avantajına sahiptir.
JeffO

13

Burada sadece Dan Finch'in cevabını ekliyorum .

Bana puan verme, Dan Finch'e puan verme :-)

Çözüm, aşağıdaki genel uzantı yöntemini yazmaktır:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Hangi gibi kullanılır:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
Bu oldukça zekice, ama ben normal bir kullanmak daha "okunabilir" olduğunu düşünüyorum, özellikle eğer yolda kodunu korumak sonunda .NET bir as çok olmayabilir ve uzatma yöntemleri kavramına çok aşina olmayabilir . Yine de bu küçük kodu alıyorum. :)
MetalMikester

1
Bunun her iki tarafını ve orijinal soruyu tartışabilir miyim - posterlerin niyetine katılıyorum, foreach'in daha iyi okuduğu ancak bir endeksin gerekli olduğu durumlar var, ancak her zaman bir dile daha fazla sözdizimsel şeker eklemenin tersi kötü gerekli - Ben farklı bir isim ile kendi kodumda tam olarak aynı çözüm var - strings.ApplyIndexed <T> (......)
Mark Mullin

Mark Mullin'e katılıyorum, her iki tarafın da tartışabilirsiniz. Uzatma yöntemlerinin oldukça akıllıca kullanıldığını düşündüm ve soruyu paylaştım, bu yüzden paylaştım.
Jalayn

5

Bu konuda yanlış olabilir, ama her zaman foreach döngü ana nokta C ++ yani STL gün iterasyon basitleştirmek olduğunu düşündüm yani

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

bunu .NET'in Ieachum'daki foreach kullanımıyla karşılaştırın

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

ikincisi çok daha basittir ve eski tuzakların çoğunu önler. Ayrıca, neredeyse aynı kurallara uyuyor gibi görünüyorlar. Örneğin, IEnumerable içeriğini döngü içerisindeyken değiştiremezsiniz (bu, STL yolunu düşünürseniz çok daha mantıklıdır). Bununla birlikte, for döngüsü genellikle yinelemeden farklı bir mantıksal amaca sahiptir.


4
+1: birkaç kişi, foreach'in temel olarak yineleyici model üzerinde sözdizimsel şeker olduğunu hatırlar.
Steven Evers

1
Bazı farklılıklar var; .NET'te mevcut olan işaretçi manipülasyonu programcıdan oldukça derin bir şekilde gizlenmiştir, ancak C ++ örneğine çok benzeyen bir for döngüsü yazabilirsiniz:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS, yapılandırılmış bir tür olmayan .NET'te dokunduğunuz her şeyin sadece farklı sözdizimiyle (.> Yerine) POINTER olduğunu fark ederseniz değil. Aslında, bir referans tipinin bir yönteme geçirilmesi, onu bir işaretçi olarak geçirmeyle aynıdır ve referansla iletmek, onu bir çift işaretçi olarak geçirmektir. Aynı şey. En azından, ana dili C ++ olan bir kişi bu şekilde düşünüyor. Benzer şekilde, okulda İspanyolca'yı, dilin kendi ana dilinizle nasıl sözdizimsel olarak ilişkili olduğunu düşünerek öğrenirsiniz.
Jonathan Henson

* değer türü yapılandırılmış tür değil. afedersiniz.
Jonathan Henson

2

Söz konusu koleksiyon bir ise IList<T>, forendeksleme ile kullanabilirsiniz :

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Değilse, kullanabilirsiniz Select(), size endeksi veren bir aşırı yük var.

Bu da uygun değilse, bu dizini elle korumak yeterince basit, sadece iki çok kısa kod satırı olduğunu düşünüyorum. Bunun için özel olarak bir anahtar kelime oluşturmak aşırıya kaçar.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

+1, ayrıca, bir dizini döngü içinde tam olarak bir kez kullanıyorsanız , hangi dizinin gerekli olduğuna foo(++i)veya buna foo(i++)bağlı olarak bir şey yapabilirsiniz .
İş

2

Bildiğiniz tek şey koleksiyonun IEnumerable olduğunu, ancak şu ana kadar kaç öğeyi işlediğinizi (ve böylece işiniz bittiğinde toplamı) izlemeniz gerekiyorsa, temel bir döngüye birkaç satır ekleyebilirsiniz:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Ayrıca, bir foreach öğesine artan bir dizin değişkeni ekleyebilirsiniz:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Bunun daha zarif görünmesini istiyorsanız, bu ayrıntıları "gizlemek" için bir veya iki uzantı tanımlayabilirsiniz:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

Kapsam belirleme ayrıca, bloğun diğer adlarla çarpışmalarını temiz tutmak istiyorsanız da çalışır ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

Bunun için bir whiledöngü kullanıyorum. forAyrıca döngüler de işe yarıyor ve birçok insan onları tercih ediyor gibi görünüyor, ancak sadece forsabit sayıda yineleme olacak bir şeyle kullanmayı seviyorum . IEnumerables çalışma zamanında üretilebilir, bu yüzden aklıma whiledaha mantıklı geliyor. İsterseniz resmi olmayan bir kodlama kılavuzu olarak adlandırın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.