Bir foreach döngüsünün geçerli yinelemesinin dizinini nasıl elde edersiniz?


938

Bir foreach döngüsünün geçerli yinelemesini temsil eden bir değer elde etmek için C # 'da karşılaştığım nadir bir dil yapısı var mı (yakın zamanda öğrendiğim birkaç, Stack Overflow'da bazıları gibi)?

Örneğin, şu anda koşullara bağlı olarak böyle bir şey yapmak:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

1
foreach döküm erişimi genellikle bir koleksiyonda sadece dizin tabanlı erişim kullanmaktan daha optimize edilmez, ancak çoğu durumda eşit olacaktır. Foreach'in amacı, kodunuzu okunabilir hale getirmektir, ancak (genellikle) ücretsiz olmayan bir dolaylama katmanı ekler.
Brian

8
Asıl amacının, foreachtüm koleksiyonlar için endekslenebilir ( List) olup olmadıklarına ( ) bakılmaksızın ortak bir yineleme mekanizması sağlamak olduğunu söyleyebilirim Dictionary.
Brian Gideon

2
Merhaba Brian Gideon - kesinlikle katılıyorum (bu birkaç yıl önce ve ben o zamanlar çok daha az deneyimli). Bununla birlikte, Dictionaryendekslenemez olsa da, bir yinelemesi Dictionaryonu belirli bir sırayla geçirir (yani bir Numaralandırıcı, sıralı olarak elemanlar vermesi nedeniyle endekslenebilir). Bu anlamda, koleksiyon içindeki dizini değil, numaralandırma içindeki mevcut numaralandırılmış öğenin dizinini aradığımızı söyleyebiliriz (yani birinci ya da beşinci ya da son numaralandırılmış öğede olup olmadığımızı).
Matt Mitchell

4
foreach ayrıca derleyicinin derlenen koddaki her dizi erişimini kontrol eden sınırları atlamasına izin verir. Bir dizinle kullanmak, çalışma zamanının dizin erişiminizin güvenli olup olmadığını kontrol etmesini sağlar.
IvoTops

1
Ama bu yanlış. For döngüsünün yineleme değişkenini döngü içinde değiştirmezseniz, derleyici sınırlarının ne olduğunu bilir ve tekrar kontrol etmesine gerek yoktur. Bu çok yaygın bir durumdur, herhangi bir iyi derleyici bunu uygulayacaktır.
Jim Balter

Yanıtlar:


551

foreachUygulamak koleksiyonları yineleme içindir IEnumerable. Bunu, GetEnumeratordöndürülecek koleksiyonu arayarak yapar Enumerator.

Bu Numaralandırıcı bir yöntem ve bir özelliğe sahiptir:

  • MoveNext'in ()
  • şimdiki

CurrentNumaralandırıcı'nın şu anda açık olduğu nesneyi döndürür, bir sonraki nesneye MoveNextgüncellenir Current.

Bir endeks kavramı numaralandırma kavramına yabancıdır ve yapılamaz.

Bu nedenle, çoğu koleksiyon bir dizinleyici ve for döngü yapısı kullanılarak çaprazlanabilir.

Büyük ölçüde yerel bir değişken ile dizin izleme karşılaştırıldığında bu durumda bir for döngüsü kullanmayı tercih ederim.


165
"Açıkçası, bir endeks kavramı numaralandırma kavramına yabancıdır ve yapılamaz." - Bu, David B ve bcahill'in cevaplarının açıkladığı gibi saçmalık. Bir indeks bir aralıktaki bir numaralandırmadır ve birinin paralel olarak iki şeyi sıralamamasının bir nedeni yoktur ... Enumerable'ın indeksleme şekli tam olarak budur.
Jim Balter

11
Temel kod örneği:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock

22
@JimBalter: Okuduğum ilk bağlantı bu. Konumunuzu nasıl desteklediğini görmüyorum. Ayrıca cevap verirken insanlara ad-homs ve bölücü kelimeler fırlatmıyorum. Örnek olarak "saçmalık", "olağanüstü karışıklık", "tamamen yanlış ve karışık", "37 upvotes aldım" vb. 'argumentum ad verecundiam' ile başkalarına göz atmamanızı nazikçe sormak istiyorum ve bunun yerine StackOverflow'da insanları desteklemenin yollarını düşünmenizi teşvik etmek istiyorum. Jon Skeet'in burada bu konuda örnek bir vatandaş olduğunu söyleyebilirim.
Pretzel

11
Pretzel: Jon Skeet'i örnek bir vatandaş olarak kullanmanız yanlış, çünkü yaşadığım gibi kendisiyle aynı fikirde olmayan birine göz atacak (ve oylayacak). Jim Balter: Yorumlarınızdan bazıları aşırı agresifti ve puanlarınızı daha az öfke ve daha fazla eğitimle sunabilirsiniz.
Suncat2000

7
@Pretzel Jim'in amacı, elemanları bir tamsayı dizisine eşleyebildiğiniz sürece dizine ekleyebilmenizdir. Sınıfın kendisinin bir dizin depolamaması noktanın yanındadır. Ayrıca, bağlantılı liste olduğunu mu bir emir var sadece Jim'in konumunu güçlendiriyor. Tek yapmanız gereken her öğeyi sırayla numaralandırmaktır. Özellikle, yineleme sırasında bir sayıyı artırarak bunu başarabilir veya aynı uzunlukta bir tamsayı listesi oluşturabilir ve sonra bunları sıkıştırabilirsiniz (Python zipişlevinde olduğu gibi).
jpmc26

666

Ian Mercer, Phil Haack'in blogunda buna benzer bir çözüm yayınladı :

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

Bu , LINQ'ların aşırı yüklenmesini kullanarak item ( item.value) ve index ( item.i) değerlerini alır :Select

[Select Select] fonksiyonunun ikinci parametresi kaynak elemanın dizinini temsil eder.

new { i, value }Yeni yaratıyor anonim nesne .

ValueTupleC # 7.0 veya üstünü kullanıyorsanız yığın ayırmalarını kullanarak önlenebilir :

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

Ayrıca item.otomatik yıkımı kullanarak da ortadan kaldırabilirsiniz :

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

9
Bu çözüm, şablonun düzgünlüğünün önemsiz bir tasarım kaygısı olduğu ve ayrıca numaralandırılan her öğenin dizinini kullanmak istediğiniz bir Razor şablonu için iyidir. Bununla birlikte, 'sarma' dan yapılan nesne tahsisinin bir tamsayının (kaçınılmaz) artışının üstüne gider (boşluk ve zamanda) eklediğini unutmayın.
David Bullock

6
@mjsr Dokümantasyon burada .
Thorkil Holm-Jacobsen

19
Üzgünüz - bu akıllıca, ancak foreach dışında bir dizin oluşturmak ve her döngüyü artırmaktan daha okunabilir mi?
jbyrd

13
Daha sonra C # sürümleri ile tuples da kullanıyorsunuz, böylece böyle bir şeye sahip olacaksınız: Model'de foreach (var (item, i). ((V, i) => (v, i))) tuple yapısökümüyle doğrudan for-loop içindeki öğeye ve indekse (i) erişin.
Haukman

5
Birisi bana bunun neden iyi bir cevap olduğunu açıklayabilir mi (yazarken 450'den fazla upvotes)? Görebildiğim kadarıyla anlamak, bir sayacı arttırmaktan daha zordur, bu nedenle daha az bakım yapılabilir, daha fazla bellek kullanır ve muhtemelen daha yavaştır. Bir şey mi kaçırıyorum?
Zengin N

182

Son olarak C # 7, bir foreachdöngü içinde bir dizin (yani, tuples) almak için iyi bir sözdizimine sahiptir :

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Küçük bir uzatma yöntemi gerekli olacaktır:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

8
Bu cevabın hafife alınması, tuple'lerin çok daha temiz olması
Todd

7
Boş koleksiyonları işlemek için değiştirildi:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Takım

Güzel bir. Bu çözümü en çok seviyorum.
FranzHuber23

Bu en iyi cevaptır
w0ns88

2
Yöntemi Enumerated, diğer diller için kullanılan insanlar için daha tanınabilir olarak adlandırmak yararlı olabilir (ve belki de demet parametrelerinin sırasını da değiştirebilirsiniz). Değil o WithIndexbariz zaten değildir.
FernAndr

114

Böyle bir şey yapabilirdi:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

12
Bu "gerçekten" sorunu çözmez. Fikir iyi ama ek sayma değişkeninden
kaçınmıyor

Bizim for döngüsü içinde bir iade deyimi varsa, bu işe yaramaz, bunun için "ForEachWithIndex" değiştirirseniz, o zaman genel değil, döngü için düzenli yazma daha iyi
Shankar Raju

ForEachWithIndex çağrınız, bir dize ve bir dizin alan Linq Select'i kullanarak buna eşdeğerdir:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

forÇoğu durumda bir döngünün daha iyi bir seçim olduğuna dair yorumlara katılmıyorum .

foreachyararlı bir yapıdır ve forher durumda bir döngü ile değiştirilemez.

Örneğin, bir DataReader'ınız varsa ve foreachbunu kullanarak tüm kayıtlar arasında geçiş yaparsanız, otomatik olarak Dispose yöntemini çağırır ve okuyucuyu kapatır (bu daha sonra bağlantıyı otomatik olarak kapatabilir). Bu nedenle, okuyucuyu kapatmayı unutsanız bile bağlantı sızıntılarını önlediğinden daha güvenlidir.

(Her zaman okuyucuları kapatmak iyi bir uygulamadır, ancak derleyici bunu yapmazsanız yakalayamaz - tüm okuyucuları kapattığınızı garanti edemezsiniz, ancak bağlantı alarak sızıntı yapmayacağınızı daha olası hale getirebilirsiniz. foreach kullanma alışkanlığında.)

DisposeYöntemin yararlı olduğuna ilişkin örtülü çağrının başka örnekleri olabilir .


2
Bunu işaret ettiğiniz için teşekkürler. Oldukça ince. Daha fazla bilgi pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator ve msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx adresinden daha fazla bilgi alabilirsiniz .
Mark Meuer

+1. Programcılar'danforeach farklı for(ve daha yakın while) hakkında daha ayrıntılı yazıyordum .
Arseni Mourzenko

64

Değişmez Yanıt - uyarı, performans yalnızca intdizini izlemek için a kullanmak kadar iyi olmayabilir . En azından kullanmaktan daha iyidir IndexOf.

Koleksiyondaki her öğeyi dizini bilen anonim bir nesneyle sarmak için Select'in aşırı yükleme aşırı yükünü kullanmanız yeterlidir. Bu, IEnumerable uygulayan her şeye karşı yapılabilir.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

3
Cast <T> () yerine OfType <T> () kullanmanın tek nedeni, numaralandırmadaki bazı öğelerin açık bir dökümde başarısız olmalarıdır. Nesne için bu asla böyle olmayacak.
dahlbyk

13
Tabii ki, Cast yerine OfType kullanmak için başka bir neden dışında - ki asla Cast kullanmıyorum.
Amy B

36

LINQ, C # 7 ve System.ValueTupleNuGet paketini kullanarak şunları yapabilirsiniz:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

Normal foreachyapıyı kullanabilir ve bir nesnenin üyesi olarak değil, değere ve dizine doğrudan erişebilirsiniz ve her iki alanı da yalnızca döngü kapsamında tutar. Bu nedenlerle, C # 7 ve kullanabiliyorsanız bunun en iyi çözüm olduğuna inanıyorum System.ValueTuple.



Sanırım öyle değil. Bu sorunun birçok cevabı var, muhtemelen kaçırdım ya da ne yaptığını fark etmedim çünkü mantığın bir kısmını bir uzatma yöntemine ayırdı.
Pavel

1
Bu farklı çünkü .Select, LINQ'dan yerleşiktir. Kendi fonksiyonunuzu yazmak zorunda değil misiniz? Gerçi VS kurulumu "System.ValueTuple" olması gerekir.
Anton

33

@ FlySwat'ın cevabını kullanarak şu çözümü buldum:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

Numaralandırıcıyı alırsınız GetEnumeratorve sonra bir döngü kullanarak fordöngü yaparsınız . Ancak, püf noktası döngünün durumunu yapmaktır listEnumerator.MoveNext() == true.

Yana MoveNextorada bir sonraki unsurdur ve biz tekrarlatacak elemanların bitince döngü koşulu döngü Dur yapar hale ulaşılabilir true sayimm getiri yöntemi.


10
ListEnumerator.MoveNext () == true ifadesini karşılaştırmanıza gerek yoktur. Bu bilgisayara true == true olup olmadığını sormak gibidir. :) Sadece listEnumerator.MoveNext () {}
Lütfen buraya tıklayın

9
@Zesty, kesinlikle haklısın. Bu durumda, özellikle i <blahSize dışında bir koşul girmeye alışkın olmayan insanlar için bu durumda eklemenin daha okunabilir olduğunu hissettim.
Gezim

@Gezim Burada tahminleri sakıncası yok, ama bunun bir yüklem gibi görünmediğini anlıyorum.
John Dvorak

1
Numaralandırıcıyı imha etmelisiniz.
Antonín Lejsek

@ AntonínLejsek Sayıcı uygulamıyor IDisposable.
Edward Brey

27

Bir sayaç değişkeni kullanmanın yanlış bir yanı yoktur. Aslında, kullanmak ister for, foreach whileya dobir karşı değişken bir yere ilan ve artmalıdır.

Bu nedenle, uygun şekilde dizine eklenmiş bir koleksiyonunuz olduğundan emin değilseniz bu deyimi kullanın:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

Else kullanımı bu bir sen eğer biliyorum senin o dizine eklenebilir toplama endeksi erişimi için O (1) 'dir (onun için olacak Arrayve muhtemelen için List<T>(dokümantasyon demiyor), şart olmamakla birlikte (örneğin diğer türleri için LinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

IEnumeratorÇağırma MoveNext()ve sorgulama yoluyla 'elle' çalıştırmak asla gerekli olmamalıdır Current- foreachbu özel rahatsızlığı kurtarıyor ... öğeleri atlamanız gerekiyorsa, sadece continuedöngü gövdesinde bir kullanın .

Ve sadece şeyiyle, sen ne bağlı yapıyor endeksinize, sen Paralel LINQ kullanmak olabilir (yukarıdaki yapılar esneklik bol sunuyoruz):

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

Biz kullanmak AsParallel()zaten 2014 çünkü, yukarıda ve biz hız süslemeye bu çoklu çekirdek en iyi şekilde yapmak istiyorum. Ayrıca, 'sıralı' LINQ için, sadece bir ForEach()uzatma yöntemi alırsınız List<T>veArray ... ve foreachçirkin sözdizimi için hala tek iş parçacıklı çalıştığınız için bunu kullanmanın basit bir işlem yapmaktan daha iyi olduğu açık değildir .


26

Orijinal numaralandırıcıyı, dizin bilgilerini içeren başka bir numarayla sarabilirsiniz.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

İşte ForEachHelpersınıfın kodu .

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

Bu aslında öğenin dizinini döndürmez. Bunun yerine, yalnızca listenin bir alt listesi olabilecek numaralandırılmış listenin içindeki dizini döndürür ve böylece yalnızca alt liste ve liste eşit büyüklükte olduğunda doğru veriler verir. Temel olarak, koleksiyonun içinde istenen türde olmayan nesneler olduğunda, dizininiz yanlış olur.
Lucas B

7
@Lucas: Hayır, ancak mevcut her bir yinelemenin dizinini döndürecektir. Soru buydu.
Brian Gideon

20

İşte bu sorun için yeni bulduğum bir çözüm

Orijinal kod:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Güncellenmiş kod

enumerable.ForEach((item, index) => blah(item, index));

Genişletme Yöntemi:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

16

Sadece kendi indeksinizi ekleyin. Basit tutun.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

15

Herhangi bir IEnumerable için değil, sadece bir Liste için çalışacak, ancak LINQ'da şu var:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@Jonathan Harika bir cevap olduğunu söylemedim, sadece sorduğunu yapmanın mümkün olduğunu gösterdim dedim :)

@Graphain Hızlı olmasını beklemiyordum - Nasıl çalıştığından tam olarak emin değilim, karşılaştırmalar helluvalotu olacak eşleşen bir nesne bulmak için her seferinde tüm listeyi tekrarlayabilir.

Bununla birlikte, List, sayımla birlikte her nesnenin bir dizinini tutabilir.

Jonathan daha ayrıntılı bir fikre sahip gibi gözüküyor, eğer detaylandırırsa?

Öte yandan, her yerde daha basit, daha basit ve daha uyarlanabilir bir yerde kalmak daha iyi olur.


5
Ağır düşüşte emin değilim. Elbette performans bunu yasaklıyor, ancak soruyu cevapladınız!
Matt Mitchell

4
Bununla ilgili başka bir sorun, yalnızca listedeki öğeler benzersizse işe yaramasıdır.
CodesInChaos

14

C # 7 nihayet bize bunu yapmanın zarif bir yolunu sunuyor:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

Evet ve MS bu tür şeyleri yerli hale getirmek için CLR / BCL'yi genişletmelidir.
Todd

14

Neden foreach ?!

List kullanıyorsanız foreach yerine for yöntemini kullanmak en basit yöntemdir :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

Veya foreach kullanmak istiyorsanız:

foreach (string m in myList)
{
     // Do something...
}

Her döngünün dizinini bilmek için bunu kullanabilirsiniz:

myList.indexOf(m)

8
indexOf çözümü yinelenen listeler için geçersiz ve çok yavaş.
timtam

2
Kaçınılması gereken sorun, IEnumerable ürününü birden çok kez geçtiğiniz yerdir, örneğin öğelerin sayısını ve ardından her öğeyi almak için. IEnumerable, örneğin bir veritabanı sorgusu sonucu olduğunda bunun sonuçları vardır.
David Clarke

2
myList.IndexOf () değeri O (n) olduğundan döngünüz O (n ^ 2) olur.
Patrick Beard

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Bu destekleyici koleksiyonlar için işe yarar IList.


68
İki problem: 1) Bu, O(n^2)çoğu uygulamada IndexOfolduğu için O(n). 2) Listede yinelenen öğeler varsa bu başarısız olur.
CodesInChaos

15
Not: O (n ^ 2), büyük bir koleksiyon için bunun çok yavaş olabileceği anlamına gelir.
O'Rooney

IndexOf yöntemini kullanmak için harika bir buluş! Ben foreach döngü dizin (sayı) almak için aradığını budur! Big Thx
Mitja Bonca

19
Tanrım, umarım kullanmadın! :( Oluşturmak istemediğiniz değişkeni KULLANIR - aslında, n + 1 ints oluşturur çünkü bu işlev geri dönmek için bir tane oluşturmak zorundadır - ve bu arama indeksi çok, çok daha yavaştır her adımda bir tam sayı artış operasyonu. Neden insanlar bu cevabı oylamayacak?
canahari

13
Bu cevabı kullanma, yorumlardan birinde bahsedilen zor gerçeği buldum. Msgstr "Listede yinelenen öğeler varsa bu başarısız olur." !!!
Bruce

9

Bu şekilde yapıyorum, basitliği / kısalığı için güzel, ancak döngü gövdesinde çok şey yapıyorsanız obj.Value, oldukça hızlı bir şekilde eski olacak.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

8

Bu cevap: doğrudan dil desteği için C # dil ekibinin lobisini yapın.

Başlıca cevap şöyle diyor:

Açıkçası, bir endeks kavramı numaralandırma kavramına yabancıdır ve yapılamaz.

Bu, mevcut C # dil sürümü (2020) için geçerli olsa da, bu kavramsal bir CLR / Dil sınırı değildir, yapılabilir.

Microsoft C # dil geliştirme ekibi, yeni bir Interface IIndexedEnumerable için destek ekleyerek yeni bir C # dili özelliği oluşturabilir

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

foreach ()Kullanılırsa ve varsa , with var indexderleyici öğe koleksiyonunun IIndexedEnumerablearabirimi bildirmesini bekler . Arabirim yoksa, derleyici, kaynağı, dizini izleme koduna ekleyen bir IndexedEnumerable nesnesiyle çok dolgu yapabilir.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

Daha sonra CLR, yalnızca withanahtar kelime belirtildiğinde ve kaynak doğrudan uygulanmıyorsa kullanılan dahili dizin izlemeye sahip olacak şekilde güncellenebilirIIndexedEnumerable

Neden:

  • Foreach daha hoş görünüyor ve iş uygulamalarında, foreach döngüleri nadiren bir performans darboğazıdır
  • Foreach bellekte daha verimli olabilir. Her adımda yeni koleksiyonlara dönüştürmek yerine bir işlevler hattına sahip olmak. Daha az CPU önbellek hatası ve daha az çöp toplama olduğunda birkaç CPU döngüsü kullanıp kullanmadığını kim takar?
  • Kodlayıcının dizin izleme kodu eklemesini gerektiren, güzelliği bozar
  • Uygulanması oldukça kolaydır (lütfen Microsoft) ve geriye dönük olarak uyumludur

Çoğu insan burada Microsoft çalışanları olmasa da, bu bir doğru cevap, bu tür bir özellik eklemek Microsoft kulis edebilirsiniz. Bir uzantı işleviyle zaten kendi yineleyicinizi oluşturabilir ve tuples kullanabilirsiniz , ancak Microsoft, uzantı işlevinden kaçınmak için sözdizimsel şekeri serpebilir


Bekleyin, bu dil özelliği zaten var mı, yoksa gelecek için mi önerildi?
Pavel

1
@Pavel Cevabı net olacak şekilde güncelledim. Bu cevap "Açıkça, bir endeks kavramı numaralandırma kavramına yabancıdır ve yapılamaz."
Todd

6

Koleksiyon bir listeyse, List.IndexOf komutunu aşağıdaki gibi kullanabilirsiniz:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

13
Ve şimdi algoritma O (n ^ 2) (daha kötüsü değilse). Bunu kullanmadan önce çok dikkatli düşünürdüm . Ayrıca @crucible'ın cevabının kopyası
BradleyDotNET

1
@BradleyDotNET tam olarak doğru, bu sürümü kullanmayın.
Oskar

1
Buna dikkat et! Listenizde yinelenen bir öğe varsa, birincinin konumunu alacaktır!
Sonhja

5

Anahtar kelime continuegüvenli yapı kullanmak daha iyidir

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

5

Döngünüzü şöyle yazabilirsiniz:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Aşağıdaki yapı ve uzatma yöntemini ekledikten sonra.

Yapı ve uzantı yöntemi, Numaralandırılabilir'i kapsar.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

3

Bu soruna yönelik çözümüm bir uzantı yöntemi WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

Gibi kullanın

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

Ben kullanmayı tercih ediyorum struct(dizin, öğe) çifti için.
CodesInChaos

3

İlgi için, Phil Haack bunun bir örneğini Razor Templated Temsilcisi bağlamında yazdı ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Etkili bir şekilde yineleme bir "IteratedItem" sınıfında saran bir uzantı yöntemi yazar (aşağıya bakın), hem dizin hem de yineleme sırasında öğeye erişim sağlar.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

Bununla birlikte, tek bir işlem (yani lambda olarak sağlanabilen bir işlem) yapıyorsanız, Razor olmayan bir ortamda iyi olurken, Razor olmayan bağlamlarda for / foreach sözdiziminin sağlam bir yedeği olmayacaktır. .


3

Bunun oldukça verimli olması gerektiğini düşünmüyorum, ama işe yarıyor:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

Bunu LINQPad'de oluşturdum :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

Ayrıca string.joinşunları da kullanabilirsiniz :

var joinResult = string.Join(",", listOfNames);

String.join öğesini c # içinde de kullanabilirsiniz. Örneğin: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance

1
Birisi bana bu O (n * n) şeyin ne olduğunu açıklayabilir mi?
Axel

@Axel, temel olarak, sonucu hesaplamak için gereken işlemlerin karesel olarak artması anlamına gelir, yani nöğeler varsa, işlemler n * nveya n-bölümlüdür. en.wikipedia.org/wiki/…
Richard Hansell

2

Bir foreach döngüsünün mevcut yinelemesinin değerini almanın bir yolu olduğuna inanmıyorum. Kendinizi saymak, en iyi yol gibi görünüyor.

Neden bilmek istersiniz diye sorabilir miyim?

Görünüşe göre en çok üç şeyden birini yapıyor olacaksınız:

1) Nesneyi koleksiyondan almak, ancak bu durumda zaten var.

2) Nesneleri daha sonra işlemek için saymak ... koleksiyonlarda kullanabileceğiniz bir Count özelliği vardır.

3) Nesnedeki bir özelliği döngüdeki sırasına göre ayarlama ... buna rağmen nesneyi koleksiyona eklediğinizde kolayca ayarlayabilirsiniz.


4) Birkaç kez vurduğum dava, ilk veya son geçişte yapılması gereken farklı bir şey - yazdırılacak nesnelerin bir listesini söyleyin ve öğeler arasında virgül gerekir, ancak son öğeden sonra değil.
Loren Pechtel

2

Koleksiyonunuz nesnenin dizinini bir yöntemle döndüremediği sürece, tek yol örneğinizdeki gibi bir sayaç kullanmaktır.

Ancak, dizinlerle çalışırken, sorunun tek makul cevabı bir for döngüsü kullanmaktır. Başka herhangi bir şey zaman ve mekan karmaşıklığından bahsetmemek yerine kod karmaşıklığı getirir.


2

Böyle bir şeye ne dersin? MyEnumerable boşsa, myDelimitedString öğesinin boş olabileceğini unutmayın.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

Burada birden fazla sorun var. A) ekstra destek. B) string concat + = döngü yineleme başına.
enorl76

2

Sadece bu problemi yaşadım, ancak benim durumumdaki problemi düşünmek, beklenen çözümle ilgisiz en iyi çözümü verdi.

Oldukça yaygın bir durum olabilir, temel olarak, bir kaynak listesinden okuyorum ve bir hedef listesinde bunlara dayalı nesneler oluşturuyorum, ancak, önce kaynak öğelerin geçerli olup olmadığını kontrol etmek ve herhangi bir satırını döndürmek istiyorum hata. İlk bakışta, Current özelliğindeki nesnenin numaralandırıcısına dizin almak istiyorum, ancak, bu öğeleri kopyaladığım için, geçerli dizini zaten geçerli hedeften dolaylı olarak biliyorum. Açıkçası hedef nesnenize bağlıdır, ama benim için bir Liste'ydi ve büyük olasılıkla ICollection'ı uygulayacak.

yani

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

Her zaman uygulanabilir değil, ancak bahsetmeye değer çoğu zaman yeterli.

Her neyse, mesele şu ki bazen mantığınızda zaten belli olmayan bir çözüm var ...


2

Soruya dayalı dizin bilgileri ile ne yapmaya çalıştığınızdan emin değildim. Ancak, C # 'da, genellikle IEnumerable.Select yöntemini kullanarak dizini istediğiniz her şeyden çıkarabilirsiniz. Örneğin, bir değerin tek veya çift olması gibi bir şey kullanabilirim.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Bu, öğenin listede tek (1) veya çift (0) olup olmadığına göre adıyla bir sözlük verir.

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.