Чи можуть параметри бути постійними?


83

Я шукаю еквівалент C # Java final. Чи існує?

Чи є у C # щось подібне:

public Foo(final int bar);

У наведеному вище прикладі barє змінною лише для читання і не може бути змінена Foo(). Чи є спосіб зробити це в C #?

Наприклад, може бути , у мене є довгий метод , який буде працювати з x, yі zкоординати деяких об'єктів (Інтс). Я хочу бути абсолютно впевненим, що функція жодним чином не змінює ці значення, тим самим пошкоджуючи дані. Таким чином, я хотів би оголосити їх лише для читання.

public Foo(int x, int y, int z) {
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.
}

Дублікат stackoverflow.com/questions/2125591/… (і має чудову відповідь Еріка Ліпперта, до речі)
casperOne

12
Я не думаю, що його точна копія. Це питання стосується різниці між передачею за посиланням та передачею за значенням. Я думаю, що Росарх, можливо, використав поганий приклад коду для того, що він намагався передати.
Corey Sunwold

@Rosarch: Що я розумію під словом "остаточний", так це те, що ти можеш робити більше дій із об'єктом, яким би це не було. Я розумію, що "фінал", застосований до класу, буде еквівалентністю "запечатаного" в C #. Але в чому полягає перевага чи реальне використання цього ключового слова "остаточне"? Я бачив це майже в будь-якому місці колись у вихідному коді JAVA.
Will Marcouiller

3
@Will Marcouiller З кінцевим ключовим словом це не об’єкт, який не може змінитись, оскільки ви можете використовувати метод, який змінюється у внутрішніх об’єктах, це посилання на об’єкт, який не може змінитися. У випадку ситуації передачі значення, як у наведеному прикладі, це не дозволить x ++ бути допустимою операцією, оскільки значення змінюється. Перевагою було б те, що ви отримуєте перевірку осудності часу компіляції, щоб переконатися, що ніщо не встановлює значення різним. Однак на моєму досвіді мені ніколи не була потрібна така функція.
Corey Sunwold

+1 @Corey Sunwold: Дякую за цю точність. Наразі я знаю лише зразки між C # та JAVA, знаючи, що C # "успадкував" від JAVA у своїх концепціях. Однак це заохочує мене дізнатися більше про JAVA, оскільки я знаю, що вона широко поширена у всьому світі.
Will Marcouiller

Відповіді:


60

На жаль, ви не можете зробити це в C #.

constКлючове слово може бути використано тільки для локальних змінних і полів.

readonlyКлючове слово може бути використано тільки на полях.

ПРИМІТКА. Мова Java також підтримує наявність кінцевих параметрів методу. Ця функція не існує в C #.

з http://www.25hoursaday.com/CsharpVsJava.html

РЕДАГУВАТИ (2019/08/13): Я кидаю це для наочності, оскільки це прийнято і є найвищим у списку. Тепер це можливо з inпараметрами. Детальніше див . Відповідь нижче.


1
"На жаль"? Чим це відрізнятиметься від поточної можливості?
Джон Сондерс,

31
@ Джон Сондерс Нещасний, бо Росарх шукає функцію, яка не існує.
Corey Sunwold

7
@ Джон: Він не є постійним у методі. Це питання.
Полудень Шовковий

6
Його єдина константа поза сферою дії цього методу. Ігноруючи, передано воно за посиланням чи за значенням, Rosarch не хоче, щоб параметр змінювався в межах методу. Ось ключова різниця.
Corey Sunwold

5
@silky: читаючи його допис, це не те, про що він просить. Він просить переконатися, що метод не змінює фактичні параметри.
Джон Сондерс,

30

Тепер це можливо в C # версії 7.2:

Ви можете використовувати inключове слово в підписі методу. Документація MSDN .

inКлючове слово повинно бути додано до вказівки аргументу методу.

Приклад, дійсний метод у C # 7.2:

public long Add(in long x, in long y)
{
    return x + y;
}

Незважаючи на те, що заборонено:

public long Add(in long x, in long y)
{
    x = 10; // It is not allowed to modify an in-argument.
    return x + y;
}

Наступна помилка буде показана при спробі модифікувати xабо yз тих пір, як вони позначені in:

Неможливо призначити змінну 'in long', оскільки вона є змінною лише для читання

Позначення аргументу inзасобами:

Цей метод не змінює значення аргументу, що використовується як цей параметр.


3
Звичайно, це не корисно для еталонних типів. Використовуючи inключове слово для параметрів з цими типами, не допускайте лише їх присвоєння. Не заважайте змінювати його доступні поля або властивості! Чи правий я?
ABS

5
Зверніть увагу, що він добре працює лише зі структурами / значеннями, а НЕ з класами, де ви можете змінити екземпляр, який є ref, так що це набагато гірше. І ви не отримуєте жодних попереджень. Використовуйте з обережністю. blog.dunnhq.com/index.php/2017/12/15/…
jeromej

8

Ось коротка та мила відповідь, яка, ймовірно, набере багато голосів проти. Я не прочитав усіх постів та коментарів, тому, будь ласка, пробачте мене, якщо це було запропоновано раніше.

Чому б не взяти свої параметри і не передати їх в об'єкт, який представляє їх незмінними, а потім використовувати цей об'єкт у своєму методі?

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

Удачі :-)


1
Оскільки учасників все ще можна змінювати. У цьому C ++ код показує , що: int* p; *p = 0;. Це буде компілюватися та працювати до помилки сегментації.
Коул Джонсон,

Я голосував проти, тому що це не рішення проблеми. Ви також можете зберегти аргументи в голові функції та порівняти їх наприкінці та створити виняток щодо змін. Я збираюся тримати це в своїй задній кишені, якщо коли-небудь буде змагання з найгіршим рішенням :)
Рік О'Ші

8

Відповідь: C # не має такої функції const, як C ++.

Я згоден з Беннеттом Діплом.

Ключове слово const дуже корисне. У цьому прикладі ви використовували int, і люди не розуміють вашу думку. Але чому, якщо ваш параметр - це величезний і складний об’єкт, який не можна змінити всередині цієї функції? Це використання ключового слова const: параметр не може змінюватися всередині цього методу, оскільки [яка б тут не була причина], що для цього методу не має значення. Ключове слово Const дуже потужне, і мені дуже не вистачає цього в C #.


7

Почну з intпорції. intце тип значення, а в .Net це означає, що ви дійсно маєте справу з копією. По-справжньому дивне обмеження в дизайні говорити методу "Ви можете отримати копію цього значення. Це ваша копія, а не моя; я більше ніколи її не побачу. Але ви не можете змінити копію". У виклику методу мається на увазі, що копіювання цього значення - це нормально, інакше ми не могли б безпечно викликати метод. Якщо методу потрібен оригінал, залиште його впроваджувачеві, щоб він зробив копію, щоб зберегти його. Або надайте методу значення, або не надайте методу значення. Не йдіть між собою всіми бажаючими.

Перейдемо до посилальних типів. Тепер це стає трохи заплутаним. Ви маєте на увазі постійне посилання, де саме посилання неможливо змінити, або повністю заблокований, незмінний об'єкт? Якщо перший, посилання в .Net за замовчуванням передаються за значенням. Тобто ви отримуєте копію посилання. Отже, ми маємо фактично таку ж ситуацію, як і для типів цінностей. Якщо реалізатору знадобиться оригінал посилання, він може зберегти його самостійно.

Це просто залишає у нас постійний (заблокований / незмінний) об’єкт. З точки зору виконання, це може здатися нормальним, але як компілятор його застосовувати? Оскільки властивості та методи можуть мати побічні ефекти, ви, по суті, обмежуєтесь доступом до полів лише для читання. Такий об’єкт навряд чи буде дуже цікавим.


1
Я підтримав вас за нерозуміння питання; йдеться не про зміну значення ПІСЛЯ дзвінка, а про зміну його ВНУТРІ.
Полудень Шовковий

2
@silky - я не неправильно зрозумів питання. Я також говорю про функцію. Я кажу, що це дивне обмеження надсилати до функції, оскільки це насправді нічого не зупиняє. Якщо хтось забуде змінений оригінальний параметр, він з такою ж ймовірністю забуде змінену копію.
Joel Coehoorn

1
Я не погоджуюсь. Просто надавши коментар, що пояснює голосування проти.
Полудень Шовковий

DateTime має лише доступ до полів для читання, але це все одно цікаво. Він має багато методів, які повертають нові екземпляри. Багато функціональних мов уникають методів з побічними ефектами і віддають перевагу незмінним типам, на мою думку, це полегшує дотримання коду.
Девін Гарнер

DateTime також все ще є типом значення, а не посилальним типом.
Joel Coehoorn

5

Створіть інтерфейс для свого класу, який має лише доступ до властивостей лише для читання. Тоді нехай ваш параметр буде цього інтерфейсу, а не самого класу. Приклад:

public interface IExample
{
    int ReadonlyValue { get; }
}

public class Example : IExample
{
    public int Value { get; set; }
    public int ReadonlyValue { get { return this.Value; } }
}


public void Foo(IExample example)
{
    // Now only has access to the get accessors for the properties
}

Для конструкцій створіть загальну обгортку const.

public struct Const<T>
{
    public T Value { get; private set; }

    public Const(T value)
    {
        this.Value = value;
    }
}

public Foo(Const<float> X, Const<float> Y, Const<float> Z)
{
// Can only read these values
}

Хоча варто зазначити, що дивно, що ти хочеш робити те, що ти просиш зробити щодо структур, як автор методу ти повинен очікувати знати, що відбувається в цьому методі. Це не вплине на значення, передані для їх модифікації в методі, тому ваша єдина проблема полягає в тому, щоб поводитись належним чином у методі, який ви пишете. Настає момент, коли пильність і чистий код є ключовим фактором, а не забезпечення дотримання const та інших подібних правил.


Це насправді досить розумно. Також я хочу зазначити, що ви можете успадкувати кілька інтерфейсів одночасно - але ви можете успадкувати лише один клас.
Наталі Адамс

Це корисно ... і таким самим чином полегшує роздратування через C #. Дякую
Jimmyt1988

3

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

Усім тим, хто болить зараз натиснути кнопку проти, будь ласка, ознайомтесь з думками цих корифеїв щодо цієї теми:


Зверніть увагу, що для того, щоб забезпечити це, вам не потрібна умова іменування; Ви завжди можете зробити це, використовуючи інструмент аналізу після факту (не чудово, але тим не менше). Я вважаю, що жандарм ( mono-project.com/Gendarme ) має для цього правило, і, мабуть, StyleCop / FxCop теж.
Полудень Шовковий

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

Двоє наболілих користувачів SO до цього могло бути гірше.
Ганс Пассант,

2

Я знаю, що це може бути трохи пізно. Але для людей, які все ще шукають для цього інші шляхи, може існувати інший спосіб обійти це обмеження стандарту C #. Ми могли б написати клас обгортки ReadOnly <T> де T: struct. З неявним перетворенням на базовий тип Т. Але лише явне перетворення на клас обгортки <T>. Що буде застосовувати помилки компілятора, якщо розробник намагається неявно встановити значення типу ReadOnly <T>. Як я продемонструю два можливі варіанти використання нижче.

USAGE 1 потрібно змінити визначення абонента. Це використання буде використано лише для тестування на правильність коду ваших функцій "TestCalled". На рівні випуску / збірках ви не повинні його використовувати. Оскільки у великих масштабах математичні операції можуть перевершити перетворення і зробити ваш код повільним. Я б не використовував його, але лише для демонстрації це опублікував.

ВИКОРИСТАННЯ 2, яке я б запропонував, демонструє використання функції налагодження проти випуску у функції TestCalled2. Також не було б перетворення функції TestCaller при використанні цього підходу, але для цього потрібно трохи більше кодування визначень TestCaller2 з використанням кондиціонування компілятора. Ви можете помітити помилки компілятора в конфігурації налагодження, тоді як при конфігурації випуску весь код у функції TestCalled2 буде успішно скомпільовано.

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct
{
  private VT value;
  public ReadOnly(VT value)
  {
    this.value = value;
  }
  public static implicit operator VT(ReadOnly<VT> rvalue)
  {
    return rvalue.value;
  }
  public static explicit operator ReadOnly<VT>(VT rvalue)
  {
    return new ReadOnly<VT>(rvalue);
  }
}

public static class TestFunctionArguments
{
  static void TestCall()
  {
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  }

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  {
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  {
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  {
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }

}

Було б краще, якщо ReadOnly був би struct, а VT міг бути одночасно struct і class, таким чином, якщо VT struct, тоді значення передаватиметься за значенням, а якщо це клас, то значення буде передаватися за посиланням, і якщо ви хочете, щоб це було як java's final, оператор ReadOnly повинен бути неявним.
Томер Волберг,

0

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

Чи можете ви створити параметр, значення якого неможливо призначити в методі або властивості якого неможливо встановити в межах методу? Ні. Ви не можете запобігти присвоєнню значення в методі, але ви можете запобігти встановленню його властивостей, створивши незмінний тип.

Питання не в тому, чи можна призначити параметр або його властивості в рамках методу. Питання в тому, що це буде, коли метод вийде.

Будь-які зовнішні дані будуть змінені лише в тому випадку, якщо ви передасте клас і зміните одне з його властивостей, або якщо ви передасте значення за допомогою ключового слова ref. Ситуація, яку ви окреслили, ні того, ні іншого.


Я б поставив +1, за винятком коментаря про незмінність ... наявність незмінного типу не виконає того, що він шукає.
Адам Робінсон

Звичайно, це було б за замовчуванням. Незмінний тип, не переданий за посиланням, гарантує, що значення, поза методом, не зміниться.
Девід Мортон,

1
Правда, але його приклад стосується зміни значення всередині методу. У його прикладі коду x - це Int32, який незмінний, але йому все одно дозволено писати x++. Тобто він намагається запобігти перепризначенню параметра, який є ортогональним до змінності значення параметра.
itowlson

1
@silky Напевно дивно проголосувати за три відповіді, які неправильно зрозуміли одне і те ж питання. Можливо, не винен читач, що питання було неправильно зрозуміле. Ви знаєте, коли чоловік розлучається зі своєю третьою дружиною, це, як правило, не вини дружин.
Девід Мортон,

2
@David: Це мета системи голосування. Замовити за актуальністю. Я не розумію, чому це мало би стосуватися вас, щоб вас виправили.
Полудень Шовковий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.