Deklaracja stałej tablicy

457

Czy można napisać coś podobnego do poniższego?

public const string[] Titles = { "German", "Spanish", "Corrects", "Wrongs" };
Jaime Oro
źródło
statyczny może być użyty, publiczny ciąg statyczny [] Tytuły = nowy ciąg [] {"niemiecki", "hiszpański"};
Ray

Odpowiedzi:

690

Tak, ale musisz to zadeklarować readonlyzamiast const:

public static readonly string[] Titles = { "German", "Spanish", "Corrects", "Wrongs" };

Powodem jest to, że constmożna go zastosować tylko do pola, którego wartość jest znana w czasie kompilacji. Inicjalizator tablicy, który pokazałeś, nie jest stałym wyrażeniem w języku C #, więc powoduje błąd kompilatora.

Zadeklarowanie go readonlyrozwiązuje ten problem, ponieważ wartość nie jest inicjowana do czasu wykonania (chociaż gwarantuje się, że zainicjowała się przed pierwszym użyciem tablicy).

W zależności od tego, co ostatecznie chcesz osiągnąć, możesz również rozważyć deklarację wyliczenia:

public enum Titles { German, Spanish, Corrects, Wrongs };
Cody Gray
źródło
115
Zauważ, że tablica tutaj nie jest oczywiście tylko do odczytu; Tytuły [2] = „walijski”; działałoby dobrze w środowisku uruchomieniowym
Marc Gravell
46
Prawdopodobnie chcesz też, aby był statyczny
tymtam
4
co powiesz na zadeklarowanie tablicy „const” w ciele metody, a nie w klasie?
serhio
19
Przepraszam za głosowanie w dół, ale const oznacza również statyczność. Zadeklarowanie tablicy jako tylko do odczytu nie jest zbliżone do obejścia. musi readonly staticmieć dowolne podobieństwo do żądanej semantyki.
Anton
3
@Anton, czy ty i twoi „obserwatorzy” usunęliście głosowanie? Dla mnie staticnie jest wymagane, aby działało, po prostu dodaje możliwość odwoływania się Titlesbez instancji, ale usuwa możliwość zmiany wartości dla różnych instancji (np. Możesz mieć konstruktor z parametrem w zależności od tego, którą wartość zmieniasz w tym readonlypolu).
Sinatr
57

Możesz zadeklarować tablicę jako readonly, ale pamiętaj, że możesz zmienić element readonlytablicy.

public readonly string[] Titles = { "German", "Spanish", "Corrects", "Wrongs" };
...
Titles[0] = "bla";

Rozważ użycie enum, jak sugerował Cody, lub IList.

public readonly IList<string> ITitles = new List<string> {"German", "Spanish", "Corrects", "Wrongs" }.AsReadOnly();
Branimir
źródło
31
W .NET 4.5 i nowszych można zadeklarować listę jako IReadOnlyList <ciąg> zamiast IList <ciąg>.
Grzegorz Smulko
Żeby było jasne, nadal możesz zmieniać wartości w IReadOnlyList (po prostu nie dodawaj ani nie usuwaj elementów). Ale tak, zadeklarowanie go jako IReadOnlyList byłoby lepsze niż IList.
KevinVictor
Pytanie dotyczy constNIE readonly...
Yousha Aleayoub
50

Nie można utworzyć tablicy „const”, ponieważ tablice są obiektami i można je tworzyć tylko w czasie wykonywania, a stałe encje są rozwiązywane w czasie kompilacji.

Zamiast tego możesz zadeklarować tablicę jako „tylko do odczytu”. Ma to taki sam efekt jak const, z tą różnicą, że wartość można ustawić w czasie wykonywania. Można go ustawić tylko raz, a następnie jest to wartość tylko do odczytu (tj. Stała).

JAiro
źródło
2
W tym poście podkreślono, dlaczego tablic nie można zadeklarować jako stałe
Radderz,
1
Możliwe jest zadeklarowanie stałej tablicy; problemem jest zainicjowanie go stałą wartością. Jedynym działającym przykładem, który przychodzi mi na myśl, jest const int[] a = null;niezbyt przydatny, ale rzeczywiście przykład stałej tablicowej.
waldrumpus
TO jest jedyna poprawna odpowiedź.
Yousha Aleayoub
17

Od wersji C # 6 możesz pisać w następujący sposób:

public static string[] Titles => new string[] { "German", "Spanish", "Corrects", "Wrongs" };

Zobacz także: C #: Nowy i ulepszony C # 6.0 (konkretnie rozdział „Funkcje i właściwości wyrażania”)

Spowoduje to utworzenie właściwości statycznej tylko do odczytu, ale nadal pozwoli ci zmienić zawartość zwracanej tablicy, ale gdy ponownie wywołasz właściwość, otrzymasz ponownie oryginalną, niezmienioną tablicę.

Dla wyjaśnienia, ten kod jest taki sam jak (lub w rzeczywistości skrót):

public static string[] Titles
{
    get { return new string[] { "German", "Spanish", "Corrects", "Wrongs" }; }
}

Należy pamiętać, że takie podejście ma wadę: nowa tablica jest tworzona przy każdym odwołaniu, więc jeśli używasz bardzo dużej tablicy, może to nie być najbardziej wydajne rozwiązanie. Ale jeśli ponownie użyjesz tej samej tablicy (na przykład umieszczając ją w prywatnym atrybucie), ponownie otworzy ona możliwość zmiany zawartości tablicy.

Jeśli chcesz mieć niezmienną tablicę (lub listę), możesz również użyć:

public static IReadOnlyList<string> Titles { get; } = new string[] { "German", "Spanish", "Corrects", "Wrongs" };

Ale nadal wiąże się to z ryzykiem zmian, ponieważ nadal możesz rzutować go z powrotem na ciąg [] i modyfikować zawartość jako taką:

((string[]) Titles)[1] = "French";
Mjepson
źródło
1
Jaki jest zysk z używania nieruchomości zamiast pola w tym przypadku?
nicolay.anykienko
2
Pole nie może zwrócić nowego obiektu przy każdym wywołaniu. Właściwość jest w zasadzie rodzajem „ukrytej funkcji”.
mjepson,
1
Jeśli odwoływałeś się do ostatniej opcji, można to zrobić za pomocą pola lub właściwości, ale ponieważ jest ona publiczna, wolę właściwość. Nigdy nie używam pola publicznego od czasu wprowadzenia właściwości.
mjepson,
1
Jest jeszcze jedna wada pierwszego podejścia: błąd kompilacji nie zostanie wyświetlony, jeśli Titles[0]na przykład zostaniesz przypisany - w efekcie próba przypisania jest cicho ignorowana. W połączeniu z nieefektywnością ponownego tworzenia tablicy za każdym razem zastanawiam się, czy takie podejście jest w ogóle warte. Natomiast drugie podejście jest wydajne i musisz zejść z drogi, aby pokonać niezmienność.
mklement0
6

Jeśli zadeklarujesz tablicę za interfejsem IReadOnlyList, otrzymasz stałą tablicę o stałych wartościach deklarowanych w czasie wykonywania:

public readonly IReadOnlyList<string> Titles = new [] {"German", "Spanish", "Corrects", "Wrongs" };

Dostępne w .NET 4.5 i wyższych.

Richard Garside
źródło
6

Rozwiązanie .NET Framework v4.5 +, które poprawia odpowiedź tdbeckett :

using System.Collections.ObjectModel;

// ...

public ReadOnlyCollection<string> Titles { get; } = new ReadOnlyCollection<string>(
  new string[] { "German", "Spanish", "Corrects", "Wrongs" }
);

Uwaga: Biorąc pod uwagę, że kolekcja jest stała koncepcyjnie, sensowne może staticbyć zadeklarowanie jej na poziomie klasy .

Powyższe:

  • Inicjuje niejawne pole zaplecza właściwości jeden raz z tablicą.

    • Zauważ, że { get; }- tj. Deklarowanie tylko modułu pobierania właściwości - powoduje, że sama właściwość jest domyślnie tylko do odczytu (próba połączenia readonlyz nią { get; }jest w rzeczywistości błędem składni).

    • Alternatywnie, możesz po prostu pominąć { get; }i dodać, readonlyaby utworzyć pole zamiast właściwości, jak w pytaniu, ale ujawnianie publicznych elementów danych jako właściwości zamiast pól jest dobrym nawykiem do tworzenia.

  • Tworzy array- jak struktury (pozwalając indeksowanego dostępu ), który jest naprawdę solidnie i tylko do odczytu (koncepcyjnie stały, raz utworzone), zarówno w odniesieniu do:

    • zapobieganie modyfikacji kolekcji jako całości (na przykład poprzez usunięcie lub dodanie elementów lub przypisanie nowej kolekcji do zmiennej).
    • zapobieganie modyfikacji poszczególnych elementów .
      (Nawet pośrednia zmiana nie jest to możliwe - w przeciwieństwie z IReadOnlyList<T>roztworu, gdzie obsada może być wykorzystywane w celu uzyskania dostępu do zapisu elementów , jak pokazano na pomocne odpowiedzi mjepsen męska . Ta sama luka dotyczy w interfejsie , który, mimo podobieństwa w nazwie do klasy , nie obsługuje nawet dostępu indeksowanego , przez co zasadniczo nie nadaje się do zapewniania dostępu podobnego do tablicy).(string[])
      IReadOnlyCollection<T> ReadOnlyCollection
mklement0
źródło
1
@mortb: Niestety IReadOnlyCollectionnie obsługuje dostępu indeksowanego, więc nie można go tutaj użyć. Dodatkowo, podobnie jak IReadOnlyList(który ma dostęp zindeksowany) jest podatny na manipulowanie elementami poprzez rzutowanie z powrotem na string[]. Innymi słowy: ReadOnlyCollection(na które nie można rzutować tablicy ciągów) jest najbardziej niezawodnym rozwiązaniem. Nieużywanie gettera jest opcją (i zaktualizowałem odpowiedź, aby to zauważyć), ale w przypadku danych publicznych prawdopodobnie lepiej trzymać się właściwości.
mklement0
5

Na moje potrzeby określam statictablicę zamiast niemożliwej consti działa ona: public static string[] Titles = { "German", "Spanish", "Corrects", "Wrongs" };

ALZ
źródło
1
Proste usunięcie constz przykładu OP również działa, ale to (lub twoja odpowiedź) pozwala zmienić zarówno: Titlesinstancję, jak i dowolną wartość. Jaki jest sens tej odpowiedzi?
Sinatr
@ Sinatr, odpowiedziałem na to 3 lata temu, kiedy zacząłem pracować w C #. Zostawiłem to, teraz jestem w świecie Java. Być może zapomniałem dodaćreadonly
ALZ
Po chwili zastanowienia, odpowiedź jest bezpośrednim jak zrobić kod OP pracy , bez żadnych const/ readonlyrozważania, po prostu dzięki czemu działa (jak gdyby constbył to błąd składni). Dla niektórych osób wydaje się to być cenną odpowiedzią (być może próbowali również constprzez pomyłkę?).
Sinatr
5

Możesz zastosować inne podejście: zdefiniuj stały ciąg reprezentujący tablicę, a następnie podziel ciąg na tablicę, gdy jej potrzebujesz, np.

const string DefaultDistances = "5,10,15,20,25,30,40,50";
public static readonly string[] distances = DefaultDistances.Split(',');

Takie podejście daje stałą, którą można zapisać w konfiguracji i w razie potrzeby przekształcić w tablicę.

Alastair
źródło
8
Myślę, że koszt przeprowadzenia podziału znacznie przewyższa wszelkie korzyści wynikające z definicji const. Ale +1 za wyjątkowe podejście i nieszablonowe myślenie! ;)
Radderz
Już miałem opublikować to samo rozwiązanie, a potem zobaczyłem to, w przeciwieństwie do ostrych i negatywnych uwag, było to idealne rozwiązanie w moim scenariuszu, w którym musiałem przekazać const do atrybutu, a następnie podzieliłem wartość w konstruktorze atrybutów na dostać to, czego potrzebowałem. i nie widzę powodu, dla którego będzie to miało koszt wydajności, ponieważ atrybuty nie są tworzone dla każdej instancji.
Kalpesh Popat
4

W trosce o kompletność, teraz mamy do dyspozycji również ImmutableArrays. To powinno być naprawdę niezmienne:

public readonly static ImmutableArray<string> Tiles = ImmutableArray.Create(new[] { "German", "Spanish", "Corrects", "Wrongs" });

Wymaga System.Collections.Immutable odniesienia NuGet

https://msdn.microsoft.com/en-us/library/mt452182(v=vs.111).aspx

Shurik
źródło
3

Jest to sposób na robienie tego, co chcesz:

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;

public ReadOnlyCollection<string> Titles { get { return new List<string> { "German", "Spanish", "Corrects", "Wrongs" }.AsReadOnly();}}

Jest to bardzo podobne do robienia tablicy tylko do odczytu.

tdbeckett
źródło
8
Możesz to po prostu zrobić jako public static readonly ReadOnlyCollection<String> Titles = new List<String> { "German", "Spanish", "Corrects", "Wrongs" }.AsReadOnly();; nie ma potrzeby ponownego tworzenia listy przy każdym pobieraniu, jeśli i tak zostanie to ReadOnlyCollection.
Nyerguds,
3

To jedyna poprawna odpowiedź. Obecnie nie możesz tego zrobić.

Wszystkie pozostałe odpowiedzi sugerują użycie statycznych zmiennych tylko do odczytu, które są podobne , ale nie takie same jak stała. Stała jest na stałe zakodowana w zestawie. Statyczną zmienną tylko do odczytu można ustawić raz, prawdopodobnie podczas inicjalizacji obiektu.

Czasami są one wymienne, ale nie zawsze.

Roger Hill
źródło
2

Wierzę, że możesz to zrobić tylko do odczytu.

skaz
źródło
2

Tablice są prawdopodobnie jedną z tych rzeczy, które można ocenić tylko w czasie wykonywania. Stałe muszą być oceniane w czasie kompilacji. Spróbuj użyć „tylko do odczytu” zamiast „const”.

nemke
źródło
0

Alternatywnie, aby obejść problem z elementami, które można modyfikować za pomocą tablicy tylko do odczytu, można zamiast tego użyć właściwości statycznej. (Poszczególne elementy można nadal zmieniać, ale zmiany te zostaną wprowadzone tylko w lokalnej kopii tablicy).

public static string[] Titles 
{
    get
    {
        return new string[] { "German", "Spanish", "Corrects", "Wrongs"};
    }
}

Oczywiście nie będzie to szczególnie wydajne, ponieważ za każdym razem tworzona jest nowa tablica ciągów.

Niecka
źródło
0

Najlepsza alternatywa:

public static readonly byte[] ZeroHash = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Herman Schoenfeld
źródło