Почему в C # недопустимы параметры const?

102

Это выглядит странно, особенно для разработчиков на C ++. В C ++ мы обычно отмечали параметр как const, чтобы быть уверенным, что его состояние не будет изменено в методе. Существуют также другие причины, специфичные для C ++, например, передача const refдля передачи по ссылке и уверенности в том, что состояние не будет изменено. Но почему мы не можем пометить как параметры метода const в C #?

Почему я не могу объявить свой метод следующим образом?

    ....
    static void TestMethod1(const MyClass val)
    {}
    ....
    static void TestMethod2(const int val)
    {}
    ....
Инкогнито
источник
В C ++ это всегда было предохранителем, и никогда не было по-настоящему неотъемлемой частью языка, как я его видел. Разработчики C # явно не думали, что это приносит большую пользу.
Noldorin
3
Более широкое обсуждение этого вопроса: «Const-корректность в C #»: stackoverflow.com/questions/114149/const-correctness-in-c
tenpn
Есть для типов значений [out / ref], но не для ссылочных типов. Это интересный вопрос.
kenny
4
Как в этом вопросе могут быть теги C # и независимые от языка теги?
CJ7
1
Неизменяемые данные и функции без побочных эффектов легче рассуждать. Вам может понравиться F # fsharpforfunandprofit.com/posts/is-your-language-unreasonable
полковник Паник,

Ответы:

59

В дополнение к другим хорошим ответам я добавлю еще одну причину, по которой не следует добавлять константу в стиле C в C #. Ты сказал:

мы помечаем параметр как const, чтобы быть уверенным, что его состояние не изменится в методе.

Если бы const действительно это сделала, было бы здорово. Конст этого не делает. Константа - это ложь!

Const не дает никаких гарантий, что я действительно могу его использовать. Предположим, у вас есть метод, который принимает константу. Есть два автора кода: человек, пишущий вызывающий, и человек, пишущий вызываемого . Автор вызываемого объекта заставил метод принимать константу. Что, по мнению двух авторов, является неизменным относительно объекта?

Ничего. Вызываемый может отбросить константу и изменить объект, поэтому вызывающий не имеет гарантии, что вызов метода, который принимает константу, на самом деле не изменит ее. Точно так же вызываемый объект не может предполагать, что содержимое объекта не будет изменяться на протяжении действия вызываемого объекта; вызываемый может вызвать какой-либо изменяющий метод для псевдонима, отличного от const, для объекта const, и теперь так называемый объект const изменился .

Константа в стиле C не гарантирует, что объект не изменится и, следовательно, сломан. Теперь у C уже есть слабая система типов, в которой вы можете переинтерпретировать приведение двойника к int, если вы действительно этого хотите, поэтому неудивительно, что он также имеет слабую систему типов по отношению к const. Но C # был разработан, чтобы иметь хорошую систему типов, систему типов, в которой, когда вы говорите «эта переменная содержит строку», переменная фактически содержит ссылку на строку (или null). Мы абсолютно не хотим добавлять модификатор "const" в стиле C в систему типов, потому что мы не хотим, чтобы система типов была ложью . Мы хотим, чтобы система типов была сильной, чтобы вы могли правильно рассуждать о своем коде.

Const в C - это руководство ; в основном это означает: «Вы можете поверить, что я не буду пытаться видоизменить эту вещь». Этого не должно быть в системе типов ; материал в системе типов должен быть фактом об объекте, о котором вы можете рассуждать, а не руководством к его использованию.

Не поймите меня неправильно; просто потому, что const в C глубоко сломан, не означает, что вся концепция бесполезна. Я бы хотел увидеть действительно правильные и полезные форму аннотации «const» в C #, аннотацию, которую люди и компиляторы могут использовать, чтобы помочь им понять код, и которую среда выполнения могла бы использовать для таких вещей, как автоматическая параллелизация и другие расширенные оптимизации.

Например, представьте, что вы можете «нарисовать рамку» вокруг фрагмента кода и сказать: «Я гарантирую, что этот фрагмент кода не выполняет никаких изменений ни в одном поле этого класса» таким образом, чтобы это могло быть проверено компилятором. Или нарисуйте рамку с надписью «этот чистый метод изменяет внутреннее состояние объекта, но никак не наблюдаемым за пределами рамки». Такой объект не может быть безопасно многопоточным автоматически, но он может быть автоматически мемоизирован . Есть всевозможные интересные аннотации, которые мы могли бы добавить в код, которые позволят провести обширную оптимизацию и более глубокое понимание. Мы можем добиться большего, чем слабая аннотация const в стиле C.

Однако подчеркиваю, что это всего лишь домыслы . У нас нет твердых планов по включению такой функции в какую-либо гипотетическую будущую версию C #, если она вообще есть, о которой мы так или иначе не объявили. Это то, что я хотел бы увидеть, и то, что может потребоваться в связи с предстоящим акцентом на многоядерные вычисления, но ничто из этого никоим образом не должно толковаться как предсказание или гарантия какой-либо конкретной функции или будущего направления для C #.

Теперь, если то, что вы хотите, - это просто аннотация к локальной переменной, которая является параметром, который говорит, что «значение этого параметра не изменяется на протяжении всего метода», тогда, конечно, это будет легко сделать. Мы могли бы поддерживать локальные переменные и параметры «только для чтения», которые инициализировались бы один раз, а также ошибку времени компиляции для изменения в методе. Переменная, объявленная оператором using, уже является такой локальной; мы могли бы добавить необязательную аннотацию ко всем локальным переменным и параметрам, чтобы они действовали как «использующие» переменные. Это никогда не было функцией с очень высоким приоритетом, поэтому она никогда не была реализована.

Эрик Липперт
источник
34
Извините, но я считаю этот аргумент очень слабым. Тот факт, что разработчик может солгать, нельзя предотвратить ни на каком языке. void foo (const A & a), который изменяет объект a, не отличается от int countApples (Basket b), который возвращает количество груш. Это просто ошибка, ошибка, которая компилируется на любом языке.
Алессандро Теруцци,
55
«Вызываемый волен отбросить константу …» эээ… (° _ °) Это довольно поверхностный аргумент, который вы здесь приводите. Вызываемый также может просто начать записывать случайные ячейки памяти с помощью 0xDEADBEEF. Но и то , и другое было бы очень плохим программированием, и любой современный компилятор быстро поймал бы их. В будущем при написании ответов, пожалуйста, не путайте то, что технически возможно при использовании языковых бэкдоров, с тем, что возможно в реальном реальном коде. Подобная искаженная информация сбивает с толку менее опытных читателей и снижает качество информации о SO.
Слипп Д. Томпсон
8
«Const в C - это руководство;» Ссылка на источник для некоторых вещей, которые вы говорите, действительно поможет в вашем случае. По моему опыту в сфере образования и промышленности, процитированное утверждение - фигня - const в C является такой же обязательной встроенной функцией, как и сама система типов - у обоих есть лазейки, чтобы обмануть их, когда это необходимо (как и все в C), но ожидается для использования книгой в 99,99% кода.
Слипп Д. Томпсон
29
Этот ответ - причина, по которой я иногда ненавижу дизайн C #. Разработчики языка абсолютно уверены, что они намного умнее других разработчиков, и стараются чрезмерно защитить их от ошибок. Очевидно, что ключевое слово const работает только во время компиляции, потому что в C ++ у нас есть прямой доступ к памяти и мы можем делать все, что захотим, и у нас есть несколько способов обойти объявление const. Но в обычных ситуациях этого не делают. Кстати, «частный» и «защищенный» в C # также не дают никаких гарантий, поскольку мы можем получить доступ к этим методам или полям с помощью отражения.
BlackOverlord
13
@BlackOverlord: (1) Желание разработать язык, свойства которого естественным образом приводят разработчиков к успеху, а не к неудачам, мотивировано желанием создавать полезные инструменты , а не верой в то, что дизайнеры умнее своих клиентов, как вы предполагаете. (2) Отражение не является частью языка C #; это часть среды выполнения; доступ к отражению ограничен системой безопасности среды выполнения. Коду с низким уровнем доверия не предоставляется возможность выполнять частную рефлексию. Эти гарантии , предоставляемые модификаторы доступа предназначены для низкого кода доверия ; Код с высоким уровнем доверия может делать все, что угодно.
Эрик Липперт,
24

Одна из причин, по которой в C # нет корректной константы, заключается в том, что она не существует на уровне времени выполнения. Помните, что в C # 1.0 не было никаких функций, если они не были частью среды выполнения.

И несколько причин, по которым в CLR нет понятия корректности констант, например:

  1. Это усложняет время выполнения; кроме того, в JVM этого тоже не было, и CLR в основном начиналась как проект по созданию среды выполнения, подобной JVM, а не среде выполнения, подобной C ++.
  2. Если есть константная корректность на уровне времени выполнения, в BCL должна быть константная корректность, в противном случае эта функция практически бессмысленна для .NET Framework.
  3. Но если BCL требует константной корректности, каждый язык поверх CLR должен поддерживать константную корректность (VB, JavaScript, Python, Ruby, F # и т. Д.). Этого не произойдет.

Корректность констант в значительной степени является особенностью языка, присутствующей только в C ++. Таким образом, это в значительной степени сводится к той же аргументации, почему CLR не требует проверенных исключений (что является функцией только языка Java).

Кроме того, я не думаю, что вы можете ввести такую ​​фундаментальную функцию системы типов в управляемую среду без нарушения обратной совместимости. Так что не рассчитывайте, что корректность const когда-либо войдет в мир C #.

Рубен
источник
Вы уверены, что в JVM этого нет. Насколько я знаю, он есть. Вы можете попробовать public static void TestMethod (final int val) в Java.
Incognito,
2
Final не совсем const. Вы по-прежнему можете изменять поля ссылочного IIRC, но не само значение / ссылку. (Который в любом случае передается по значению, так что это скорее уловка компилятора, которая не позволяет вам изменить значение параметра.)
Рубен
1
ваша третья точка верна лишь отчасти. наличие постоянных параметров для вызовов BCL не будет критическим изменением, поскольку они просто более точно описывают контракт. «Этот метод не изменит состояние объекта» в отличие от «Этот метод требует, чтобы вы передали константное значение», что было бы нарушением
Rune FS
2
Он применяется внутри метода, поэтому введение его в BCL не будет серьезным изменением.
Rune FS
1
Даже если среда выполнения не может обеспечить его соблюдение, было бы полезно иметь атрибут, который указывал, следует ли разрешать / ожидать функции записи в refпараметр (особенно, в случае структурных методов / свойств this). Если в методе специально указано, что он не будет записывать в refпараметр, компилятор должен разрешить передачу значений только для чтения. И наоборот, если в методе указано, что он будет писать this, компилятор не должен позволять вызывать такой метод для значений, доступных только для чтения.
supercat 09
12

Я считаю, что есть две причины, по которым C # некорректно const.

Первое - это понятность . Немногие программисты на C ++ понимают константную корректность. Простой пример const int argмило, но я также виделchar * const * const arg - постоянный указатель на постоянные указатели на непостоянные символы. Постоянная корректность указателей на функции - это совершенно новый уровень обфускации.

Во-вторых, аргументы класса - это ссылки, передаваемые по значению. Это означает, что необходимо иметь дело с двумя уровнями константности без явно четкого синтаксиса . Аналогичным камнем преткновения являются коллекции (и коллекции коллекций и т. Д.).

Const-корректность - важная часть системы типов C ++. Теоретически его можно было бы добавить в C # как нечто, что проверяется только во время компиляции (его не нужно добавлять в CLR и он не повлияет на BCL, если не было включено понятие методов константных членов) .

Однако я считаю, что это маловероятно: вторую причину (синтаксис) будет довольно сложно решить, что сделало бы первую причину (понятность) еще большей проблемой.

Стивен Клири
источник
1
Вы правы в том, что CLR на самом деле не нужно об этом беспокоиться, поскольку можно использовать modoptи modreqна уровне метаданных для хранения этой информации (как это уже делает C + / CLI - см. System.Runtime.CompilerServices.IsConst); и это можно было бы тривиально распространить на методы const.
Павел Минаев
Это того стоит, но нужно делать безопасным способом. Упомянутая вами константа Deep / Shallow открывает целую банку червей.
user1496062 01
В C ++, где на самом деле манипулируют ссылками, я вижу ваш аргумент о наличии двух типов const. Однако в C # (где вам нужно пройти через черный ход, чтобы использовать "указатели") мне кажется достаточно ясным, что существует четко определенное значение для ссылочных типов (т.е. классов) и четко определенное, хотя и другое значение для значения типы (т.е. примитивы, структуры)
Assimilater
2
Программисты, не понимающие константности, не должны программировать на C ++.
Стив Уайт
5

constозначает «константу времени компиляции» в C #, а не «только для чтения, но, возможно, изменяемую другим кодом», как в C ++. Примерный аналог C ++const в C # есть readonly, но он применим только к полям. Кроме того, в C # нет C ++ - подобного понятия корректности const.

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

Павел Минаев
источник
Итак, вы думаете, что MS считает «шумом» постоянные параметры в методах.
Incognito,
1
Я не уверен, что такое «шум» - я бы назвал это «высоким соотношением цена / качество». Но, конечно, вам понадобится кто-то из команды C #, чтобы сказать вам наверняка.
Павел Минаев
Итак, насколько я понимаю, ваш аргумент состоит в том, что константность была исключена в C # для простоты. Подумай об этом.
Стив Уайт
2

Это возможно, начиная с C # 7.2 (декабрь 2017 г. с Visual Studio 2017 15.5).

Только для структур и основных типов ! , а не для членов классов.

Вы должны использовать inдля отправки аргумента в качестве ввода по ссылке. Видеть:

См. Https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-parameter-modifier

Для вашего примера:

....
static void TestMethod1(in MyStruct val)
{
    val = new MyStruct;  // Error CS8331 Cannot assign to variable 'in MyStruct' because it is a readonly variable
    val.member = 0;  // Error CS8332 Cannot assign to a member of variable 'MyStruct' because it is a readonly variable
}
....
static void TestMethod2(in int val)
{
    val = 0;  // Error CS8331 Cannot assign to variable 'in int' because it is a readonly variable
}
....
isgoed
источник