Могут ли параметры быть постоянными?

85

Я ищу 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
Я не думаю, что это точная копия. Этот вопрос касается разницы между передачей по ссылке и передачей по значению. Я думаю, что Розарх, возможно, использовал плохой пример кода для того, что он пытался донести.
Кори Санволд,
@Rosarch: Под словом «финал» я понимаю то, что вы можете больше не выполнять никаких действий с объектом, что бы это ни было. Я понимаю, что «окончательный», применяемый к классу, будет эквивалентен «запечатанному» в C #. Но какова же польза или реальное использование этого ключевого слова final? Я когда-нибудь видел это почти в любом месте исходного кода JAVA.
Уилл Маркуиллер,
3
@Will Marcouiller. С ключевым словом final это не тот объект, который нельзя изменить, потому что вы можете использовать метод, который изменяет внутреннюю структуру объекта, это ссылка на объект, который не может измениться. В случае передачи по значению, как в приведенном примере, это помешает x ++ быть допустимой операцией, поскольку значение будет изменяться. Преимущество состоит в том, что вы получаете проверку работоспособности во время компиляции, чтобы убедиться, что ничто не устанавливает другое значение. Однако, по моему опыту, мне никогда не требовалась такая функция.
Кори Санволд,
+1 @ Кори Санволд: Спасибо за точность. На данный момент я знаю только сходства между C # и JAVA, зная, что C # "унаследован" от JAVA в своих концепциях. Тем не менее, это побуждает меня больше узнавать о JAVA, поскольку я знаю, что она широко распространена по всему миру.
Уилл Маркуиллер,

Ответы:

62

К сожалению, на C # это невозможно.

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

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

ПРИМЕЧАНИЕ. Язык Java также поддерживает окончательные параметры метода. Эта функция отсутствует в C #.

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

РЕДАКТИРОВАТЬ (2019/08/13): я добавляю это для наглядности, так как это принято и занимает первое место в списке. Теперь это возможно с inпараметрами. См. Ответ под этим для подробностей.

Кори Санволд
источник
1
"К сожалению"? Чем это будет отличаться от текущих возможностей?
Джон Сондерс,
31
@John Saunders: Жалко, потому что Розарх ищет функцию, которой не существует.
Кори Санволд,
7
@John: Это не всегда в рамках метода. Вот в чем проблема.
Noon Silk
6
Его единственная константа, выходящая за рамки этого метода. Игнорируя, был ли он передан по ссылке или по значению, Розарх не хочет, чтобы параметр изменялся в рамках метода. Это ключевое отличие.
Кори Санволд,
5
@silky: читая его пост, он не об этом просит. Он просит убедиться, что метод не изменяет фактические параметры.
Джон Сондерс,
32

Теперь это возможно в 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:

Невозможно присвоить переменной long, потому что это переменная только для чтения

Обозначение аргумента inозначает:

Этот метод не изменяет значение аргумента, используемого в качестве этого параметра.

Максимум
источник
4
Конечно, это бесполезно для ссылочных типов. Использование inключевого слова для параметров с этими типами предотвращает только присвоение им. Не препятствуйте изменению доступных полей или свойств! Я прав?
ABS
5
Обратите внимание, что он хорошо работает только со структурой / значениями, а НЕ с классами, где вы можете изменить экземпляр, который находится по ссылке, так что это намного хуже. И вообще никаких предупреждений. Используйте с осторожностью. blog.dunnhq.com/index.php/2017/12/15/…
jeromej
8

Вот короткий и приятный ответ, который, вероятно, получит много голосов против. Я не прочитал все сообщения и комментарии, поэтому, пожалуйста, простите меня, если это было предложено ранее.

Почему бы не взять ваши параметры и не передать их объекту, который представляет их как неизменяемые, а затем использовать этот объект в своем методе?

Я понимаю, что это, вероятно, очень очевидный обходной путь, который уже рассматривался, и OP пытается избежать этого, задавая этот вопрос, но я чувствовал, что он, тем не менее, должен быть здесь ...

Удачи :-)

Беннетт Дилл
источник
1
Потому что члены все еще могут быть изменены. В этом C ++ код показывает , что: int* p; *p = 0;. Это будет компилироваться и работать до ошибки сегментации.
Коул Джонсон
Я проголосовал против, потому что это не решение проблемы. Вы также можете сохранить аргументы в заголовке функции и сравнить их в конце и выдать исключение при изменении. Я собираюсь держать это в заднем кармане, если когда-нибудь будет конкурс наихудшего решения :)
Рик О'Ши
8

Ответ: C # не имеет функций const, как C ++.

Я согласен с Беннетом Диллом.

Ключевое слово const очень полезно. В этом примере вы использовали int, и люди не поняли вашей точки зрения. Но почему, если ваш параметр - это огромный и сложный объект пользователя, который нельзя изменить внутри этой функции? Это использование ключевого слова const: параметр не может измениться внутри этого метода, потому что [по какой-либо причине] это не имеет значения для этого метода. Ключевое слово Const очень мощное, и мне его очень не хватает в C #.

Мишель Ваз Рамос
источник
7

Начну с intпорции. int- это тип значения, а в .Net это означает, что вы действительно имеете дело с копией. Это действительно странное ограничение дизайна - сказать методу: «У вас может быть копия этого значения. Это ваша копия, а не моя; я никогда ее больше не увижу. Но вы не можете изменить копию». В вызове метода неявно подразумевается, что копирование этого значения допустимо, иначе мы не могли бы безопасно вызвать метод. Если для метода требуется оригинал, оставьте его исполнителю сделать копию для сохранения. Либо укажите значение метода, либо не укажите значение. Не делайте перерывов между делом.

Перейдем к ссылочным типам. Теперь это немного сбивает с толку. Вы имеете в виду постоянную ссылку, где сама ссылка не может быть изменена, или полностью заблокированный неизменяемый объект? В первом случае ссылки в .Net по умолчанию передаются по значению. То есть вы получаете копию справки. Таким образом, мы имеем практически такую ​​же ситуацию, что и для типов значений. Если разработчику понадобится исходная ссылка, он может оставить ее себе.

Это просто оставляет нам постоянный (заблокированный / неизменяемый) объект. Это может показаться нормальным с точки зрения времени выполнения, но как компилятор может обеспечить это? Поскольку свойства и методы могут иметь побочные эффекты, вы по существу будете ограничены доступом к полю только для чтения. Такой объект вряд ли будет очень интересным.

Джоэл Кохорн
источник
2
Я проголосовал против вас за непонимание вопроса; речь идет не об изменении значения ПОСЛЕ вызова, а об изменении его ВНУТРИ.
Noon Silk,
2
@silky - я правильно понял вопрос. Я тоже говорю о функции. Я говорю, что это странное ограничение для отправки в функцию, потому что на самом деле оно ничего не останавливает. Если кто-то забудет исходный измененный параметр, он с такой же вероятностью забудет измененную копию.
Джоэл Кохорн,
1
Я не согласен. Просто предоставьте комментарий, объясняющий голос против.
Noon Silk
DateTime имеет доступ только к полю только для чтения, но это все равно интересно. У него много методов, которые возвращают новые экземпляры. Многие функциональные языки избегают методов с побочными эффектами и предпочитают неизменяемые типы, на мой взгляд, это упрощает отслеживание кода.
Девин Гарнер
DateTime также по-прежнему является типом значения, а не ссылочным типом.
Джоэл Кохорн
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 05
3

Если вы часто сталкиваетесь с подобными проблемами, вам следует подумать о «венгерских приложениях». Хороший вид в отличие от плохого . Хотя это обычно не пытается выразить постоянство параметра метода (что слишком необычно), конечно же, нет ничего, что мешает вам добавить дополнительную букву «c» перед именем идентификатора.

Всем тем, кто хочет сейчас нажать кнопку «против», пожалуйста, прочтите мнения этих светил по этой теме:

Ганс Пассан
источник
Обратите внимание, что для обеспечения этого соглашения об именах не требуется; Вы всегда можете сделать это постфактум с помощью инструмента анализа (не очень хорошо, но тем не менее). Я считаю, что у Жандарма ( mono-project.com/Gendarme ) есть правило для этого, и, вероятно, у StyleCop / FxCop тоже.
Noon Silk,
Легко наихудшая попытка (неудачная) решения. Итак, теперь ваш параметр не только доступен для записи, он настраивает вас на сбой, солгав вам о том, что он не изменился.
Рик О'Ши
Двое больных пользователей SO пока могли быть хуже.
Ханс Пассан
2

Я знаю, что это может быть немного поздно. Но для людей, которые все еще ищут другие способы решения этой проблемы, может быть другой способ обойти это ограничение стандарта C #. Мы могли бы написать класс-оболочку ReadOnly <T> где T: struct. С неявным преобразованием в базовый тип T. Но только явное преобразование в класс-оболочку <T>. Что приведет к ошибкам компилятора, если разработчик попытается неявно установить значение типа ReadOnly <T>. Ниже я продемонстрирую два возможных использования.

ИСПОЛЬЗОВАНИЕ 1 требует изменения определения вызывающего абонента. Это использование будет использоваться только при проверке правильности кода вашей функции "TestCalled". На уровне выпуска / сборках вы не должны его использовать. Поскольку в крупномасштабных математических операциях может возникнуть избыток преобразований и замедление вашего кода. Я бы не стал его использовать, но только для демонстрации разместил его.

ИСПОЛЬЗОВАНИЕ 2, которое я предлагаю, демонстрирует использование Debug vs Release в функции 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;
  }

}
SoLaR
источник
Было бы лучше, если бы ReadOnly был структурой, а VT мог бы быть как структурой, так и классом, таким образом, если VT является структурой, тогда значение будет передаваться по значению, а если это класс, тогда значение будет передано по ссылке, и если вы хотите, чтобы оно было похоже на java final оператор ReadOnly должен быть неявным.
Томер Вольберг
0

Если структура передается в метод, если она не передана по ссылке, она не будет изменена методом, в который она передана. Так что в этом смысле да.

Можете ли вы создать параметр, значение которого не может быть присвоено в методе или свойства которого не могут быть установлены в рамках метода? Нет. Вы не можете предотвратить присвоение значения в методе, но вы можете предотвратить установку его свойств, создав неизменный тип.

Вопрос не в том, можно ли назначить параметр или его свойства внутри метода. Вопрос в том, что будет после выхода из метода.

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

Дэвид Мортон
источник
Я бы добавил +1, за исключением комментария о неизменности ... наличие неизменяемого типа не принесет того, что он ищет.
Адам Робинсон
Конечно, по умолчанию. Неизменяемый тип, не переданный по ссылке, гарантирует, что значение вне метода не изменится.
Дэвид Мортон,
1
Верно, но его пример касается изменения значения внутри метода. В его примере кода x - это Int32, который неизменен, но ему все еще разрешено писать x++. То есть он пытается предотвратить переназначение параметра, которое ортогонально изменчивости значения параметра.
itowlson
1
@silky Как-то странно отрицать три ответа, которые неправильно поняли один и тот же вопрос. Возможно, это не вина читателя, что вопрос был неправильно понят. Знаете, когда мужчина разводится со своей третьей женой, обычно это не вина жены.
Дэвид Мортон
2
@ Дэвид: Это цель системы голосования. Заказать по актуальности. Я не понимаю, почему вас должны беспокоить исправления.
Noon Silk