Корректность const в C #

Смысл константной корректности состоит в том, чтобы иметь возможность предоставить представление об экземпляре, которое не может быть изменено или удалено пользователем. Компилятор поддерживает это, указывая, когда вы нарушаете константность из константной функции или пытаетесь использовать неконстантную функцию константного объекта. Итак, не копируя константный подход, есть ли методология, которую я могу использовать в C #, которая имеет те же цели?

Я знаю о неизменности, но на самом деле это не относится к объектам контейнера, чтобы назвать лишь один пример.


person tenpn    schedule 22.09.2008    source источник
comment
На http://blogs.msdn.com/brada/archive/2004/02/04/67859.aspx   -  person Asaf R    schedule 22.09.2008
comment
А как насчет реализации const_cast на C #?   -  person kerem    schedule 11.09.2012


Ответы (7)


Я тоже сталкивался с этой проблемой много раз и в конечном итоге использовал интерфейсы.

Я думаю, что важно отказаться от идеи, что C # - это любая форма или даже эволюция C ++. Это два разных языка с почти одинаковым синтаксисом.

Я обычно выражаю «корректность констант» в C #, определяя представление класса только для чтения:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}
person Trap    schedule 22.09.2008
comment
Это не один из тех вопросов, на который есть ответ, но мне очень нравится это решение. Он элегантен, его можно адаптировать ко многим ситуациям, и его легко писать. - person tenpn; 23.09.2008
comment
Конечно, но что, если одно из ваших полей является списком или расширенным типом. Ваше решение очень быстро усложняется. - person Matt Cruikshank; 18.10.2008
comment
Возврат внутренней коллекции считается плохой практикой. Вы либо возвращаете интерфейс только для чтения для управления этим конкретным списком, доступным только для чтения, либо копию исходного списка, чтобы избежать вреда. - person Trap; 04.11.2008
comment
@Trap: в этом весь смысл вопроса. Возврат внутренней коллекции - плохая практика, потому что внешний мир может изменить ваши внутренние компоненты, в C ++ это решается с использованием const: вы обеспечиваете постоянное представление коллекции (не можете добавлять / удалять элементы, предлагает только постоянное представление ее элементы), и как таковой он безопасен (и распространен). Не нужно разрабатывать интерфейсы или другие уловки, чтобы внешний код не изменил вашу внутреннюю структуру. - person David Rodríguez - dribeas; 06.09.2009
comment
@Matt: Согласитесь, хорошая часть корректности const в том, что она в каком-то смысле «транзитивна». Вы предоставляете постоянную ссылку на контейнер, который сам заботится только о предоставлении постоянных представлений своих элементов, каждый из которых предлагает только постоянные представления своих внутренних элементов ... - person David Rodríguez - dribeas; 06.09.2009
comment
Помните, что это всего лишь приведение к удалению возможности чтения, а именно (Customer) myCustomer. Тем не менее, наверное, лучшее решение, если вы так себя ведете и не забрасываете :) - person Roman Starkov; 13.01.2010
comment
@David: Языки предоставляют такие механизмы как помощь в разработке, а не как средство, позволяющее неопытным / плохим программистам делать что-то неправильно. В C # вы добиваетесь этого естественным образом, просто используя интерфейсы, в то время как в C ++ вам нужна «дополнительная» функция, называемая «const». Мне всегда казалось, что C ++ - это тот, кто использует «трюк», а не наоборот. - person Trap; 09.04.2010
comment
@David: Кроме того, используя интерфейс только для чтения, вы явно заявляете о своих намерениях, это дизайнерское решение. Я не могу вспомнить, сколько раз я лажал из-за того, что забыл добавить ключевое слово 'const'. - person Trap; 09.04.2010
comment
@Trap, я согласен с тем, что этот механизм является вспомогательным средством для разработки, которое включает в себя обнаружение ошибок от разработчиков, опытных или нет. Я не согласен с тем, что необходимость писать интерфейсы только для чтения - лучшее решение по нескольким причинам. Во-первых, всякий раз, когда вы пишете класс на C ++, вы неявно определяете постоянный интерфейс: подмножество членов, объявленных const, без дополнительных затрат на определение отдельного интерфейса и необходимости диспетчеризации во время выполнения. То есть язык предоставляет простой способ реализации константных интерфейсов времени компиляции. - person David Rodríguez - dribeas; 09.04.2010
comment
Важно отметить, что const-ness является частью дизайна и декларирует намерение так же ясно, как и написание внешнего константного интерфейса. Кроме того, предоставление константных интерфейсов может быть сложной задачей, если ее нужно решать вручную, и может потребовать написания почти такого же количества интерфейсов, сколько классов присутствует в составном типе. Это подводит нас к вашей последней фразе: «облажались из-за того, что забыли добавить константу». Легче привыкнуть добавлять const повсюду (стоимость разработки небольшая), чем писать интерфейсы только для чтения для каждого класса. - person David Rodríguez - dribeas; 09.04.2010
comment
@David: Я не против ключевого слова const в C ++ :) Мне больше нравится, как в C # достигается константная корректность. Я думаю, что количество слов, которые вам нужно набрать, чтобы добиться цели, никогда не будет важным фактором. В качестве примечания, на каждом современном языке / IDE, который я знаю, вы можете извлечь интерфейс только для чтения несколькими щелчками мыши. - person Trap; 21.07.2010
comment
Если вы храните достаточно большой изменяемый объект внутри своего собственного объекта, как вы предоставите доступный только для чтения интерфейс для содержащего объекта? Вы создадите копию содержащегося объекта? Это то, чего вы упускаете, только когда узнаете, насколько мощными являются константные ссылки. - person David Rodríguez - dribeas; 21.07.2010
comment
Как я упоминал в комментарии №3, и если размер действительно имеет значение, контейнер может предоставить специальный интерфейс только для чтения для обработки этого конкретного и безумно большого объекта. - person Trap; 21.07.2010
comment
Одна из причин, по которой мне действительно понравился C #, когда он появился, заключалась в том, что он не отбрасывал всех прагматических функций C ++, как это сделала Java. Java пыталась делать все с интерфейсами, и это было катастрофой. Доказательство в пудинге. Со временем в Java было добавлено множество функций, которые первоначально были объявлены ересью. Интерфейсный дизайн имеет свое место, но когда он доведен до крайности, он может излишне усложнить архитектуру программы. В итоге вы тратите больше времени на написание стандартного кода и меньше - на выполнение полезных вещей. - person kgriffs; 23.11.2011
comment
Я бы предпочел IReadableCustomer в качестве имени интерфейса, а не IReadOnlyCustomer, потому что последний будет означать противоречие при наследовании, например, IReadWriteCustomer [Если бы ведущий символ имен интерфейсов был в нижнем регистре, я бы использовал iMutableCustomer, но IMutableCustomer слишком похож на ImmutableCustomer] - person supercat; 31.01.2012
comment
@supercat Почему бы просто не ICustomer вместо имени изменяемого интерфейса? Я считаю, что это соответствует шаблону именования Microsoft, например, ICollection ‹T› и IReadOnlyCollection ‹T ›. - person DavidRR; 19.09.2016
comment
@DavidRR: IList<T> не обещает, что реализации будут изменяемыми или что они не будут; обычно он лишается ковариантности из-за возможности разрешать клиентам изменять реализации, которые оказываются изменяемыми, без необходимости преобразования (хотя IList<T> поддерживает форму нарушенной ковариации с массивами: с учетом ссылок на IList<Animal> и Animal, единственный способ узнать, можно ли хранить последнее в элемент первого - это попробовать; если, например, IList<Animal> на самом деле Cat, а Animal на самом деле Dog, сохранить его в IList<Animal> не удастся. - person supercat; 19.09.2016

Чтобы получить преимущество константного безумия (или чистоты в терминах функционального программирования), вам нужно будет спроектировать свои классы таким образом, чтобы они были неизменными, как и класс String в C #.

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

person Sam    schedule 22.09.2008
comment
но неизменность действительно не масштабируется для сложных объектов, или да? - person tenpn; 22.09.2008
comment
Я бы сказал, что если бы ваш объект был настолько сложным, что неизменяемость была бы невозможна, у вас был бы хороший кандидат для рефакторинга. - person Jim Burger; 23.09.2008
comment
Я считаю, что это лучший из группы. Неизменяемые объекты используются слишком редко. - person ; 25.09.2008
comment
Не только это, но есть случаи, когда вы действительно хотите иметь изменяющиеся объекты (объекты меняются!), Но в большинстве случаев все же предлагает представление только для чтения. Неизменяемые объекты подразумевают, что всякий раз, когда вам нужно внести изменение (изменение происходит), вам нужно будет создать новый объект со всеми теми же данными помимо изменения. Представьте себе школу, в которой есть школьные комнаты, ученики ... вы хотите создавать новую школу каждый раз, когда проходит дата рождения ученика и меняется ее возраст? Или вы можете просто изменить возраст на уровне ученика, ученика на уровне комнаты, может быть, комнаты на уровне школы? - person David Rodríguez - dribeas; 06.09.2009
comment
@tenpn: Неизменяемость на самом деле невероятно хорошо масштабируется, если все сделано правильно. Одна вещь, которая действительно помогает, - это использование классов Builder для больших неизменяемых типов (Java и .NET определяют класс StringBuilder, который является лишь одним примером). - person Konrad Rudolph; 29.03.2010
comment
@KonradRudolph, допустим, у вас 4 ГБ ОЗУ, у вас есть матрица 1Kx1K с плавающей запятой. Вычислите функцию transpose (с неизменяемой матрицей). Класс тривиален, функция тоже, и почему-то у вас есть проблема - почему? - person greenoldman; 06.03.2014
comment
@greenoldman Такие вещи, на самом деле, являются довольно частным случаем - чистые языки обрабатывают их, используя особый регистр таких неизменяемых функций (что, я мог бы добавить, часто очень хорошо работает). Но да, это один из тех случаев, когда вам обычно приходится прибегать к изменчивости. Не потому, что объект сложный, заметьте - просто из-за ограничения памяти. - person Konrad Rudolph; 06.03.2014
comment
@greenoldman На самом деле, извините, позвольте мне отступить. Я выпил слишком много пинты. На уровне языка нет необходимости в специальном регистре. Вы можете иметь неизменяемый интерфейс для класса matrix и по-прежнему разработать его API так, чтобы можно было эффективно выполнять транспозицию на месте, когда выполняется самоназначение - в C ++ код m = t(m); можно заставить работать в -place, даже если auto o = t(m); не будет работать на месте и скорее создаст новый экземпляр. Если бы мне пришлось разработать матричную библиотеку, я бы ее спроектировал так. - person Konrad Rudolph; 06.03.2014
comment
@KonradRudolph, конечно, вы можете оптимизировать назначение, здесь нет проблем, но неизменяемая функция transpose работает как фабрика, поэтому она (а не вызывающая сторона) должна иметь другую матрицу, что приводит к 8 ГБ используемого пространства. И имея под рукой только неизменяемые объекты (и функции), вызывающая сторона даже не имеет возможности пожертвовать чистотой ради повышения производительности. Я хочу сказать, что неизменяемость - это не серебряная пуля и не замена изменчивости. - person greenoldman; 06.03.2014
comment
@greenoldman Повторяю, вы можете легко реализовать операцию m = t(m); так, чтобы не дополнительного места не требовалось - ни вне t, ни внутри него; он работает на месте. Конечно, m не является неизменяемым, но интерфейс t является интерфейсом неизменяемой функции. Более того, это можно обобщить, чтобы такие вызовы, как matrix f() { matrix big_mat = /* some code */; return t(big_mat); }, могли также работать, не требуя дополнительной памяти, даже если код полностью неизменяем. - person Konrad Rudolph; 06.03.2014
comment
@KonradRudolph, конечно тогда m не неизменяемый - конечно, но мы говорили о неизменяемых объектах. - person greenoldman; 06.03.2014
comment
@greenoldman Я никогда не говорил, что неизменяемость должна быть исключительной, вы тут спорите как соломинку. Но, как я сказал позже, вы можете по-прежнему сделать это полностью неизменяемым, а также на месте, см. Мой пример функции. Вам просто нужно убедиться, что предыдущий объект одновременно выходит за пределы области видимости. Вот как чисто функциональные языки решают загадку. - person Konrad Rudolph; 06.03.2014

Я просто хотел отметить для вас, что многие контейнеры System.Collections.Generics имеют метод AsReadOnly, который вернет вам неизменяемую коллекцию.

person Rick Minerich    schedule 23.09.2008
comment
По-прежнему существует проблема, заключающаяся в том, что Ts являются изменяемыми, даже если ReadOnlyCollection ‹T› нет. - person arynaq; 22.10.2018

В C # такой возможности нет. Вы можете передавать аргумент по значению или по ссылке. Сама ссылка неизменна, если вы не укажете ref . Но ссылочные данные не являются неизменяемыми. Поэтому нужно быть осторожным, если вы хотите избежать побочных эффектов.

MSDN:

Передача параметров

person aku    schedule 22.09.2008
comment
ну, я думаю, вот к чему тогда сводится вопрос: как лучше всего избежать побочных эффектов отсутствия константной структуры? - person tenpn; 22.09.2008
comment
К сожалению, здесь могут помочь только неизменяемые типы. Вы можете взглянуть на Spec # - там есть несколько интересных проверок времени компиляции. - person aku; 22.09.2008

Интерфейсы - это ответ, и они на самом деле более мощные, чем "const" в C ++. const - это универсальное решение проблемы, где «const» определяется как «не устанавливает элементы и не вызывает то, что устанавливает элементы». Это хорошее сокращение для константности во многих сценариях, но не во всех. Например, рассмотрим функцию, которая вычисляет значение на основе некоторых членов, но также кэширует результаты. В C ++ это считается неконстантным, хотя с точки зрения пользователя он по сути является константой.

Интерфейсы дают вам больше гибкости в определении конкретного подмножества возможностей, которые вы хотите предоставить от своего класса. Хотите постоянство? Просто предоставьте интерфейс без методов изменения. Хотите разрешить установку одних вещей, но не других? Предоставьте интерфейс только с этими методами.

person munificent    schedule 22.09.2008
comment
Не на 100% правильно. Константные методы C ++ могут изменять элементы, помеченные как mutable. - person Constantin; 25.09.2008
comment
Справедливо. А константное приведение позволяет избавиться от константности. Оба типа подразумевают, что даже дизайнеры C ++ понимали, что один размер подходит всем, на самом деле универсален. - person munificent; 25.09.2008
comment
Преимущество C ++ в том, что у вас есть const для большинства случаев, и вы можете реализовать интерфейсы для других. Теперь, помимо const, в C ++ есть ключевое слово mutable, которое применяется к атрибутам в качестве кэшей данных или механизмов блокировки (мьютексов и т.п.). Const is not 'не изменит никаких внутренних атрибутов), а скорее не изменит состояние объекта, воспринимаемое извне. То есть любое использование объекта до и после вызова метода const даст один и тот же результат. - person David Rodríguez - dribeas; 06.09.2009
comment
отказ от константы для изменения чего-то в C ++ - это поведение undefiend (носовые демоны). const_cast предназначен для взаимодействия с устаревшим кодом, который, будучи логически константным, не помечен как const. - person jk.; 29.03.2010

Согласитесь с некоторыми другими взглядами на использование полей только для чтения, которые вы инициализируете в конструкторе, для создания неизменяемых объектов.

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

В качестве альтернативы вы также можете добавить область доступа к свойствам, то есть общедоступное получение и защищенный набор?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }
person Stuart McConnell    schedule 25.09.2008
comment
Это разные подходы, которые на самом деле не подходят для всей проблемы. С уровнем контроля доступа вы разрешаете только наследовать классы из изменений, во многих случаях это не будет соответствующим образом моделировать реальный мир. Учитель не является производным от записи ученика, но может захотеть изменить оценки ученика, хотя ученик не может изменять оценки, но может их читать ... просто чтобы назвать простой пример. - person David Rodríguez - dribeas; 06.09.2009

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

Проблема с readonly заключается в том, что он позволяет только ссылке (указателю) быть постоянной. Упомянутая (указанная) вещь все еще может быть изменена. Это сложная часть, но выхода нет. Реализовать константные объекты означает запретить им предоставлять какие-либо изменяемые методы или свойства, но это неудобно.

См. Также Эффективный C #: 50 конкретных способов улучшить свой C # (элемент 2 - Предпочитать константу только для чтения.)

person Thomas Bratt    schedule 22.09.2008