Bir List <T> belirli bir boyuta (kapasitenin tersine) nasıl başlatılır?


112

.NET, performansı neredeyse aynı olan genel bir liste kapsayıcısı sunar (bkz. Dizilerin Performansı ve Listeler Sorusu). Ancak, başlatmada oldukça farklıdırlar.

Dizilerin varsayılan bir değerle başlatılması çok kolaydır ve tanım gereği zaten belirli bir boyuta sahiptirler:

string[] Ar = new string[10];

Bu, rastgele öğeleri güvenli bir şekilde atamanıza izin verir, örneğin:

Ar[5]="hello";

liste ile işler daha karmaşıktır. Aynı başlatmayı yapmanın iki yolunu görebiliyorum, ikisi de zarif diyeceğiniz şey değil:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

veya

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Daha temiz bir yol ne olabilir?

DÜZENLEME: Şimdiye kadar verilen cevaplar, bir listeyi önceden doldurmaktan başka bir şey olan kapasiteye atıfta bulunuyor. Örneğin, 10 kişi kapasiteli yeni oluşturulmuş bir listedeL[2]="somevalue"

DÜZENLEME 2: İnsanlar listeleri neden bu şekilde kullanmak istediğimi merak ediyorlar, çünkü amaçlanan şekilde kullanılmıyorlar. İki neden görebiliyorum:

  1. Listelerin "yeni nesil" diziler olduğu ve neredeyse hiçbir ceza olmaksızın esneklik kattığı oldukça ikna edici bir şekilde tartışılabilir. Bu nedenle varsayılan olarak kullanılmalıdır. Başlamak kadar kolay olmayabileceklerine işaret ediyorum.

  2. Şu anda yazdığım şey, daha büyük bir çerçevenin parçası olarak varsayılan işlevsellik sunan temel bir sınıf. Sunduğum varsayılan işlevde, Listenin boyutu gelişmiş olarak biliniyor ve bu nedenle bir dizi kullanabilirdim. Bununla birlikte, herhangi bir temel sınıfa onu dinamik olarak genişletme şansı sunmak istiyorum ve bu nedenle bir listeyi tercih ediyorum.


1
"DÜZENLEME: Şimdiye kadarki cevaplar, bir listeyi önceden doldurmaktan başka bir şey olan kapasiteye atıfta bulunur. Örneğin, 10 kapasite ile yeni oluşturulmuş bir listede L [2] =" bir değer "yapılamaz" Buna göre değişiklik, belki de Soru Başlığını yeniden
söylemelisiniz

Ancak, bir listeyi boş değerlerle önceden doldurmanın faydası nedir, çünkü topicstarter bunu yapmaya çalışıyor?
Frederik Gheysels

1
Konumsal haritalama bu kadar önemliyse, Dictionary <int, string> kullanmak daha mantıklı olmaz mı?
Greg D

7
Listyerine geçmez Array. Belirgin biçimde ayrı sorunları çözerler. Sabit bir boyut istiyorsanız, bir Array. A kullanırsanız List, Yanlış Yapıyorsunuz.
Zenexer

5
Her zaman "neden ihtiyacım olduğunu anlayamıyorum ..." gibi tartışmalara girmeye çalışan cevaplar buluyorum. Sadece şu anlama gelir: onu göremediniz. Mutlaka başka bir şey ifade etmez. İnsanların bir soruna daha iyi yaklaşımlar önermek istemelerine saygı duyuyorum, ancak daha alçakgönüllü bir şekilde ifade edilmelidir, örneğin "Bir listeye ihtiyacınız olduğundan emin misiniz? Belki de bize sorununuz hakkında daha fazla bilgi verdiyseniz ...". Bu şekilde hoş ve ilgi çekici hale gelir ve OP'yi sorularını geliştirmeye teşvik eder. Kazanan olun - alçakgönüllü olun.
AnorZaken

Yanıtlar:


79

Buna çok sık ihtiyacım olduğunu söyleyemem - bunu neden istediğine dair daha fazla ayrıntı verebilir misin? Muhtemelen bunu yardımcı bir sınıfa statik bir yöntem olarak koyardım:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Sen olabilir kullanmak Enumerable.Repeat(default(T), count).ToList()ama bu boyutlandırma tampon verimsiz nedeniyle olurdu.

Unutmayın ki T referans türüdür, bu depolayacak countiçin geçilen referans kopyalarını valuehepsi aynı nesneyi ifade edeceğinden öylesine - parametresi. Kullanım durumunuza bağlı olarak istediğiniz şey bu olabilir veya olmayabilir.

DÜZENLEME: Yorumlarda belirtildiği gibi, isterseniz Repeatedlisteyi doldurmak için bir döngü kullanabilirsiniz. Bu da biraz daha hızlı olur. Şahsen ben kodu Repeatdaha açıklayıcı buluyorum ve gerçek dünyada performans farkının alakasız olacağından şüpheleniyorum, ancak kilometreniz değişebilir.


1
Bunun eski bir yazı olduğunu anlıyorum ama merak ediyorum. Numaralandırılabilir.Repeat , bağlantının son kısmına göre ( dotnetperls.com/initialize-array ) bir for döngüsüne kıyasla çok daha kötüdür . Ayrıca AddRange (), msdn'ye göre O (n) karmaşıklığına sahiptir. Basit bir döngü yerine verilen çözümü kullanmak biraz üretken değil mi?
Jimmy

@Jimmy: Her iki yaklaşım da O (n) olacak ve bu yaklaşımı başarmaya çalıştığım şeyi daha açıklayıcı buluyorum. Bir döngü tercih ediyorsanız, kullanmaktan çekinmeyin.
Jon Skeet

@Jimmy: Ayrıca kriter var kullandığını unutmayın Enumerable.Repeat(...).ToArray()ki, değil ben kullanıyorum nasıl.
Jon Skeet

1
Bunu Enumerable.Repeat()şu şekilde kullandım ( Pairtıpkı C ++ eşdeğeri gibi uygulandı ): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)yan etkiyi fark ettim, Listtek bir nesneye atıfta bulunulan kopyalarla dolu. Bir öğeyi değiştirmek, bütündeki myList[i].First = 1her bir öğeyi değiştirdi List. Bu hatayı bulmam saatlerimi aldı. Bu sorunla ilgili herhangi bir çözüm biliyor musunuz (yalnızca ortak bir döngü ve kullanım dışında .Add(new Pair...)?
00zetti

1
@ Pac0, aşağıya bir cevap ekledim. Düzenlemeler reddedilebilir.
Jeremy Ray Brown

136
List<string> L = new List<string> ( new string[10] );

3
şahsen bunun en temiz yol olduğunu düşünüyorum - soru gövdesinde potansiyel olarak sakar olarak bahsedilmiş olmasına rağmen - nedenini bilmiyorum
merhaba_earth

4
+1: Değişken boyutlu bir listenin, bir dizin yoluyla doldurmadan önce sabit bir sıfır kümesiyle başlatılmasına (ve daha sonra ekstra eklemeler ekledikten sonra bir dizi uygun olmadığından) başlamaya ihtiyacım olduğu bir duruma düştüm. Bu cevap, pratik ve basit olduğu için oyumu alıyor.
Kodlama Gone

Harika cevap. @GoneCoding Sadece içten yeni başlatıldı liste olmadığını merak Lbasitçe hafızasını yeniden kullanılacaktır string[10](bir dizi de bir listelerin destek mağaza) olduğu gibi veya kendi yeni bellek tahsis edecek ve daha sonra içeriğini kopyalamak string[10]? sonraki rotayı seçerse string[10]çöp otomatik olarak toplanır L.
RBT

3
Bu yaklaşıma tek dezavantajı ise @RBT: dizi olacak değil sen anlık olarak dizinin iki kopyasını almanız için, yeniden kullanılabilir (Liste farkında gibi görünüyor gibi dahili diziler kullanır). Nadir durumlarda, çok sayıda öğeniz veya bellek kısıtlamanız varsa sorun olabilir . Ve evet, ekstra dizi, List yapıcısı tamamlanır tamamlanmaz çöp toplama için uygun olacaktır (aksi takdirde korkunç bir bellek sızıntısı olurdu). Uygun ifadesinin "hemen toplandı" anlamına gelmediğini, daha çok çöp toplama işleminin sonraki çalışması anlamına geldiğini unutmayın.
AnorZaken

2
Bu cevap 10 yeni dizge ayırır - daha sonra gerektiği gibi kopyalayarak bunların üzerinde yineler. Büyük dizilerle çalışıyorsanız; o zaman bunu, kabul edilen cevaptan iki kat daha fazla hafızaya ihtiyaç duyduğundan düşünmeyin.
UKMonkey

22

Bağımsız değişken olarak bir int ("kapasite") alan yapıcıyı kullanın:

List<string> = new List<string>(10);

DÜZENLEME: Frederik'e katıldığımı eklemeliyim. Listeyi ilk etapta onu kullanmanın ardındaki tüm mantığa aykırı bir şekilde kullanıyorsunuz.

DÜZENLEME2:

DÜZENLEME 2: Şu anda yazdığım şey, daha büyük bir çerçevenin parçası olarak varsayılan işlevsellik sunan temel bir sınıf. Sunduğum varsayılan işlevde, Listenin boyutu gelişmiş olarak biliniyor ve bu nedenle bir dizi kullanabilirdim. Bununla birlikte, herhangi bir temel sınıfa onu dinamik olarak genişletme şansı sunmak istiyorum ve bu nedenle bir listeyi tercih ediyorum.

Neden tüm boş değerlere sahip bir Listenin boyutunu bilmesi gerekir ki? Listede gerçek değerler yoksa uzunluğun 0 olmasını beklerdim. Her neyse, bunun cludgy olması sınıfın amaçlanan kullanımına aykırı olduğunu gösteriyor.


46
Bu cevap, listeye 10 boş giriş ayırmaz (bu gereklilikti), listenin yeniden boyutlandırılması gerekmeden önce (yani kapasite) 10 giriş için yer ayırır , böylece bu new List<string>(), problemin gittiği kadar farklı değildir. . Yine de bu kadar çok oy almak için
Kodlama Geçti

2
bu aşırı yüklenmiş kurucu, "boyut" veya "uzunluk" değil "başlangıç ​​kapasitesi" değeridir ve öğeleri de başlatmaz
Matt Wilko

"Birisinin buna neden ihtiyacı olsun" cevabını vermek gerekirse: Ağaç veri yapılarını derinlemesine klonlamak için şu anda buna ihtiyacım var. Bir düğüm, alt düğümlerini doldurabilir veya doldurmayabilir, ancak benim temel düğüm sınıfımın kendisini tüm alt düğümleriyle klonlayabilmesi gerekir. Falan filan, daha da karmaşık. Ancak hem hala boş listemi yoluyla doldurmam hem de list[index] = obj;diğer liste yeteneklerini kullanmam gerekiyor.
Bitterblue

3
@Bitterblue: Ayrıca, tüm değerlere önceden sahip olamayabileceğiniz her türlü önceden tahsis edilmiş eşleme. Kesinlikle kullanımlar vardır; Bu cevabı daha az tecrübeli günlerimde yazdım.
Ed S.

8

Önce istediğiniz öğe sayısıyla bir dizi oluşturun ve ardından diziyi Listeye dönüştürün.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();

7

Sabit bir değerle başlatmak istiyorsanız neden bir Liste kullanıyorsunuz? Performans uğruna ona bir başlangıç ​​kapasitesi vermek istediğinizi anlayabiliyorum, ancak bir listenin düzenli bir diziye göre avantajlarından biri, gerektiğinde büyüyebilir mi?

Bunu yaptığınızda:

List<int> = new List<int>(100);

Kapasitesi 100 tam sayı olan bir liste oluşturursunuz. Bu, siz 101. öğeyi ekleyene kadar Listenizin 'büyümesine' gerek olmayacağı anlamına gelir. Listenin temelindeki dizi 100 uzunluğuyla başlatılacaktır.


1
"Sabit bir değerle başlatmak istiyorsanız neden bir Liste kullanıyorsunuz" İyi bir nokta.
Ed S.

7
Her bir öğenin başlatıldığı ve listenin sadece bir kapasiteye değil, bir boyuta sahip olduğu bir liste istedi. Bu cevap şu anki haliyle yanlıştır.
Nic Foster

Soru, sorulma eleştirisini değil, bir cevabı gerektirir. Bazen bir dizi kullanmak yerine bunu bu şekilde yapmak için bir sebep vardır. Bunun neden böyle olabileceği ile ilgileniyorsanız, bunun için bir Stack Overflow sorusu sorulabilir.
Dino Dini

5

Listelerin içeriğini bu şekilde başlatmak aslında listelerin amacı değildir. Listeler nesneleri tutmak için tasarlanmıştır. Belirli sayıları belirli nesnelere eşlemek istiyorsanız, liste yerine karma tablo veya sözlük gibi bir anahtar-değer çifti yapısı kullanmayı düşünün.


1
Bu soruya cevap vermez, sadece sorulmaması için bir sebep verir.
Dino Dini

5

Verilerinizle konumsal bir ilişkilendirme ihtiyacını vurguluyor gibi görünüyorsunuz, bu nedenle ilişkisel bir dizi daha uygun olmaz mıydı?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";

Bu, sorulandan farklı bir soruyu yanıtlıyor.
Dino Dini

4

Listeyi sabit bir değere sahip N elemanla başlatmak istiyorsanız:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}

John Skeet'in bu teknikle arabelleğin yeniden boyutlandırılması konusundaki endişelerine bakın.
Amy B

1

Listenizi varsayılan bir değerle akıllıca başlatmak için Linq'i kullanabilirsiniz. ( David B'nin cevabına benzer .)

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Bir adım daha ileri gidin ve her dizeyi "dize 1", "dize 2", "dize 3" vb. Farklı değerlerle başlatın:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();

1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();

List <string> temp2 = new List <string> (temp); OP'nin zaten önerdiği gibi.
Ed S.

Ed - bu aslında soruya cevap veriyor - ki bu kapasite ile ilgili değil.
Boaz

Ancak OP zaten bu çözümü beğenmediğini belirtti.
Ed S.

1

Kabul edilen cevapta (yeşil onay işaretli cevap) bir sorun var.

Sorun:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Nesnenin bir kopyasını gerçekleştirmek için yukarıdaki satırı değiştirmenizi tavsiye ederim. Bununla ilgili birçok farklı makale var:

Listenizdeki her öğeyi NULL yerine varsayılan kurucu ile başlatmak istiyorsanız, aşağıdaki yöntemi ekleyin:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }

1
Bu bir "sorun" değil - referansları kopyalamanın normal davranışıdır. Durumu bilmeden, nesneyi kopyalamanın uygun olup olmadığını bilemezsiniz. Soru, listelerin kopyalarla doldurulup doldurulmaması ile ilgili değil - kapasiteye sahip bir listeyi başlatmakla ilgili. Bunun davranış açısından benim cevap açıklamak edeceğiz gelmez var, ama gerçekten bu soru bağlamında bir sorun olarak sayar sanmıyorum.
Jon Skeet

@JonSkeet, ilkel türler için uygun olan ancak başvuru türleri için uygun olmayan statik yardımcıya yanıt veriyordum. Bir listenin referans verilen aynı öğe ile başlatıldığı bir senaryo doğru görünmüyor. Bu senaryoda, listedeki her öğe öbek üzerindeki aynı nesneyi gösteriyorsa, neden listeye sahip olsun?
Jeremy Ray Brown

Örnek senaryo: Başlangıçta her öğe için "Bilinmeyen" girişiyle bir dizi listesi doldurmak istersiniz, ardından listeyi belirli öğeler için değiştirirsiniz. Bu durumda, tüm "Bilinmeyen" değerlerin aynı dizeye başvuru olması tamamen mantıklıdır. Her seferinde dizeyi klonlamak anlamsız olacaktır. Bir dizeyi aynı nesneye birden çok referansla doldurmak, genel anlamda "doğru" veya "yanlış" değildir. Okuyucular davranışın ne olduğunu bildikleri sürece , özel kullanım durumlarına uygun olup olmadığına karar vermek onlara kalmıştır .
Jon Skeet

0

IList hakkında bir bildirim: MSDN IList Açıklamalar : "IList uygulamaları üç kategoriye ayrılır: salt okunur, sabit boyutlu ve değişken boyutlu. (...). Bu arabirimin genel sürümü için bkz System.Collections.Generic.IList<T>."

IList<T>dan DEĞİL devralır yapar IList(ama List<T>her ikisi uygulamak yapar IList<T>ve IList), ancak her zaman değişken boyutlu . NET IReadOnlyList<T>4.5'ten bu yana, AFAIK dışında, aradığınız şey olabilecek sabit boyutlu bir genel Liste yoktur.


0

Bu, birim testim için kullandığım bir örnek. Bir sınıf nesnesi listesi oluşturdum. Daha sonra servisten beklediğim 'X' sayıda nesneyi eklemek için forloop kullandım. Bu şekilde, herhangi bir boyut için bir Liste ekleyebilir / başlatabilirsiniz.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

Umarım size yardımcı olmuşumdur.


-1

Biraz geç ama önerdiğiniz ilk çözüm bana çok daha temiz görünüyor: belleği iki kez ayırmıyorsunuz. List kurucusunun bile kopyalamak için dizide döngü yapması gerekir; içinde sadece boş elemanların olduğunu önceden bile bilmiyor.

1. - N - döngü N Maliyetini tahsis et: 1 * tahsis (N) + N * döngü_iterasyonu

2. - N tahsis et - N + döngüyü tahsis et () Maliyet: 2 * tahsis (N) + N * loop_iteration

Bununla birlikte, List yerleşik bir sınıf olduğundan, List'in bir döngü ataması daha hızlı olabilir, ancak C # jit-compiled sooo ...

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.