Оголосити масив const


457

Чи можна написати щось подібне до наступного?

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

static може бути використаний, public static string [] заголовки = новий рядок [] {"німецький", "іспанський"};
Рей

Відповіді:


690

Так, але вам потрібно оголосити це readonlyзамість const:

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

Причина в тому, що constйого можна застосувати лише до поля, значення якого відомо під час компіляції. Ініціалізатор масиву, який ви показали, не є постійним виразом у C #, тому він створює помилку компілятора.

Декларуючи це, readonlyвирішується ця проблема, оскільки значення не ініціалізується до часу виконання (хоча гарантовано, що воно ініціалізується до першого використання масиву).

Залежно від того, що ви, нарешті, хочете досягти, ви також можете розглянути питання про декларацію про перерахунок:

public enum Titles { German, Spanish, Corrects, Wrongs };

115
Зверніть увагу, що масив тут, звичайно, не тільки для читання; Заголовки [2] = "валлійська"; буде добре працювати під час виконання
Marc Gravell

46
Ви, мабуть, хочете і статичного
tymtam

4
як щодо оголошення масиву "const" в тілі методу, а не в класі першого?
serhio

19
Вибачте за голосування "за", що "за", але const також передбачає статичність. Оголошення масиву лише прочитаним не є близьким до обходу. це повинно readonly staticмати будь-яку схожість із запитуваною семантикою.
Антон

3
@Anton, ви та ваші "послідовники" видалили поточний запис? Мені staticне потрібно, щоб він працював, він просто додає можливість посилання, Titlesне маючи примірника, але видаляє можливість змінити значення для різних екземплярів (наприклад, ви можете мати конструктор з параметром залежно від того, що ви змінюєте значення цього readonlyполя).
Сінатр

57

Ви можете оголосити масив як readonly, але майте на увазі, що ви можете змінити елемент readonlyмасиву.

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

Подумайте про використання enum, як запропонував Коді, або IList.

public readonly IList<string> ITitles = new List<string> {"German", "Spanish", "Corrects", "Wrongs" }.AsReadOnly();

31
У .NET 4.5 та новіших версіях ви можете оголосити список як IReadOnlyList <string> замість IList <string>.
Grzegorz Smulko

Щоб зрозуміти, ви все ще можете змінити значення в IReadOnlyList (просто не додавати та не видаляти елементи). Але так, оголосити це як IReadOnlyList було б краще, ніж IList.
KevinVictor

Питання про constНЕ readonly...
Yousha Aleayoub

51

Ви не можете створити масив 'const', оскільки масиви є об'єктами, і їх можна створювати лише під час виконання, а об'єкти const вирішуються під час компіляції.

Замість цього ви можете оголосити масив як "лише для читання". Це має такий же ефект, як const, за винятком того, що значення може бути встановлено під час виконання. Він може бути встановлений лише один раз, і це значення, яке має значення лише заново (тобто const).


2
Цей пост підкреслює, чому масиви не можуть бути оголошені константами
Раддерз

1
Можна оголосити постійний масив; проблема полягає в ініціалізації її постійним значенням. Єдиний робочий приклад, який спадає на думку, - const int[] a = null;це не дуже корисний, а справді примірник константи масиву.
waldrumpus

ЦЕ єдина правильна відповідь.
Yousha Aleayoub

17

Оскільки на C # 6 ви можете написати це так:

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

Дивіться також: C #: Новий та вдосконалений C # 6.0 (конкретно розділ "Функції та властивості виразного стану")

Це зробить статичну властивість лише для читання, але вона все одно дозволить вам змінити вміст повернутого масиву, але, коли ви знову зателефонуєте у власність, ви отримаєте початковий, незмінний масив знову.

Для уточнення цей код такий же, як (або насправді це скорочення):

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

Зауважте, що у цього підходу є і зворотний бік: новий масив фактично інстанціюється для кожної посилання, тому якщо ви використовуєте дуже великий масив, це може бути не найефективнішим рішенням. Але якщо ви повторно використовуєте той самий масив (наприклад, помістивши його в приватний атрибут), він знову відкриє можливість змінити вміст масиву.

Якщо ви хочете мати незмінний масив (або список), ви також можете використовувати:

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

Але це все ще має загрозу для змін, оскільки ви все одно можете повернути його до рядка [] та змінити вміст як такий:

((string[]) Titles)[1] = "French";

1
Який прибуток від використання майна замість поля в цьому випадку?
nicolay.anykienko

2
Поле не може повернути новий об'єкт під час кожного дзвінка. Властивість в основному є свого роду "функцією в маскуванні".
mjepson

1
Якщо ви посилалися на останній варіант, це можна зробити, використовуючи і поле, або властивість, але оскільки він є загальнодоступним, я віддаю перевагу властивості. Я ніколи не використовую публічне поле з моменту введення властивостей.
mjepson

1
Є ще один недолік першого підходу: ви не отримаєте помилки компіляції, якщо призначите Titles[0], наприклад, фактично, спроба призначення тихо ігнорується. У поєднанні з неефективністю відтворення масиву кожного разу, мені цікаво, чи варто навіть такий підхід показати. Навпаки, другий підхід ефективний, і вам доведеться вийти зі свого шляху, щоб перемогти незмінність.
mklement0

6

Якщо ви оголосили масив за інтерфейсом IReadOnlyList, ви отримаєте постійний масив з постійними значеннями, який оголошується під час виконання:

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

Доступно в .NET 4.5 і новіших версіях.


6

Рішення .NET Framework v4.5 +, яке покращує відповідь tdbeckett :

using System.Collections.ObjectModel;

// ...

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

Примітка: Зважаючи на те, що колекція є концептуально постійною, можливо, має сенс staticоголосити її на рівні класу .

Вище:

  • Ініціалізує неявне резервне поле властивості один раз із масивом.

    • Зауважте, що, { get; }наприклад, декларування лише отримувача властивості - це те, що робить власність неявно лише для читання (спроба поєднання readonlyз { get; }- це насправді помилка синтаксису).

    • Крім того, ви можете просто опустити { get; }та додати, readonlyщоб створити поле замість властивості, як у питанні, але викривати учасників публічних даних як властивості, а не поля - це хороша звичка формувати.

  • Створює структуру, схожу на масив (що дозволяє індексувати доступ ), яка є справді і надійною лише для читання (концептуально константа, колись створена), обидва щодо:

    • запобігання модифікації колекції в цілому (наприклад, шляхом видалення або додавання елементів або присвоєння новій колекції змінній).
    • запобігання модифікації окремих елементів .
      (Навіть непряме зміна не представляється можливим - в відміну з IReadOnlyList<T>розчином, де литий може бути використаний , щоб отримати доступ на запис до елементів , як показано на корисний відповідь mjepsen ігрова . Те ж уразливість відноситься до до інтерфейсу , який, незважаючи на подібність у назві для класу , навіть не підтримує індексований доступ , що робить його принципово непридатним для надання доступу, подібного до масиву.)(string[])
      IReadOnlyCollection<T> ReadOnlyCollection

1
@mortb: На жаль, IReadOnlyCollectionне підтримує індексований доступ, тому його не можна використовувати тут. Крім того, як, наприклад, IReadOnlyList(який має індексований доступ), він піддається маніпуляції з елементами шляхом повернення до string[]. Іншими словами: ReadOnlyCollection(до якого ви не можете присвоїти рядковий рядок) - це найбільш надійне рішення. Не використовувати getter - це варіант (і я оновив відповідь, щоб відзначити це), але з загальнодоступними даними, можливо, краще дотримуватися властивості.
mklement0

5

Для моїх потреб я визначаю staticмасив, а не неможливий, constі він працює: public static string[] Titles = { "German", "Spanish", "Corrects", "Wrongs" };


1
Просте видалення constз прикладу OP також працює, але це (або ваша відповідь) дозволяє змінити як: Titlesекземпляр, так і будь-яке значення. Тож який сенс у цій відповіді?
Сінатр

@Sinatr, я відповів на це 3 роки тому, коли працював у C #. Я залишив це, зараз я в світі Java. Можливо, я забув додатиreadonly
ALZ

Після другої думки, ваша відповідь є прямим, як змусити код ОП працювати без будь-яких const/ readonlyміркувань, просто змусивши його працювати (як якщо constб помилка синтаксису). Для деяких людей це здається цінною відповіддю (можливо, вони також намагалися використати constпомилково?).
Синатр

5

Ви можете скористатися іншим підходом: визначте постійний рядок для представлення масиву, а потім розділіть рядок на масив, коли він вам потрібен, наприклад

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

Цей підхід дає вам константу, яка може зберігатися в конфігурації та перетворюватися в масив при необхідності.


8
Я думаю, що вартість виконання розколу набагато випереджає будь-які вигоди, визначені const. Але +1 за унікальний підхід та мислення поза межами! ;)
Раддерз

Я збирався опублікувати таке ж рішення, а потім побачив це, всупереч суворим і негативним зауваженням, це справді було ідеально для мого сценарію, де мені потрібно було передавати const на атрибут, а потім я розділив значення в конструкторі атрибутів на отримати те, що мені потрібно. і я не бачу причини, чому це буде мати вартість продуктивності, оскільки атрибути створюються не для кожного екземпляра.
Калпеш Попат


3

Це спосіб зробити те, що ви хочете:

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

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

Це дуже схоже на виконання масиву для читання.


8
Ви можете це зробити так само public static readonly ReadOnlyCollection<String> Titles = new List<String> { "German", "Spanish", "Corrects", "Wrongs" }.AsReadOnly();; не потрібно відтворювати список при кожному отриманні, якщо ви все одно зробите його ReadOnlyCollection.
Nyerguds

3

Це єдина правильна відповідь. Зараз ви не можете цього зробити.

Всі інші відповіді пропонують використовувати статичні змінні лише для читання, які схожі , але не такі, як константа. Константа важко закодована в збірку. Статична змінна лише для читання встановлюється один раз, ймовірно, як об'єкт ініціалізований.

Вони іноді взаємозамінні, але не завжди.



2

Масиви - це, мабуть, одна з тих речей, яку можна оцінити лише під час виконання. Константи повинні бути оцінені під час компіляції. Спробуйте використовувати "readonly" замість "const".


0

В якості альтернативи, щоб обійти проблему, що може бути змінена, з масивом, який читається, замість цього можна використовувати статичну властивість. (Окремі елементи все ще можуть бути змінені, але ці зміни будуть здійснені лише в локальній копії масиву.)

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

Звичайно, це не буде особливо ефективно, оскільки щоразу створюється новий рядковий масив.


0

Найкраща альтернатива:

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 };
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.