«Правильность констант» в C #

79

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

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

tenpn
источник
А как насчет реализации const_castна C #?
kerem

Ответы:

64

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

Я думаю, что важно отказаться от идеи, что 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; }
    }
}
Ловушка
источник
12
Конечно, но что, если одно из ваших полей является списком или расширенным типом. Ваше решение очень быстро усложняется.
Мэтт Крукшанк,
12
@Trap: в этом весь смысл вопроса. Возврат внутренней коллекции - плохая практика, потому что внешний мир может изменить ваши внутренние компоненты, в C ++ это решается с помощью const: вы обеспечиваете постоянное представление коллекции (не можете добавлять / удалять элементы, предлагает только постоянное представление ее элементы), и поэтому он безопасен (и распространен). Не нужно разрабатывать интерфейсы или другие уловки, чтобы внешний код не изменил вашу внутреннюю структуру.
Дэвид Родригес - дрибес
9
@Trap, я согласен с тем, что этот механизм является вспомогательным средством для разработки, которое включает в себя обнаружение ошибок от разработчиков, опытных или нет. Я не согласен с тем, что необходимость писать интерфейсы только для чтения - лучшее решение по нескольким причинам. Во-первых, всякий раз, когда вы пишете класс на C ++, вы неявно определяете константный интерфейс: подмножество объявленных членов constбез дополнительных затрат на определение отдельного интерфейса и необходимость диспетчеризации во время выполнения. То есть язык предоставляет простой способ реализации константных интерфейсов времени компиляции.
Дэвид Родригес - dribeas
10
Важно отметить, что const-ness является частью дизайна и декларирует намерение так же ясно, как и написание внешнего константного интерфейса. Кроме того, предоставление константных интерфейсов может быть сложной задачей, если ее нужно решать вручную, и может потребовать написания почти такого же количества интерфейсов, сколько классов присутствует в составном типе. Это подводит нас к вашей последней фразе: «облажались из-за того, что забыли добавить константу». Легче привыкнуть добавлять constвезде (стоимость разработки небольшая), чем писать интерфейсы только для чтения для каждого класса.
Дэвид Родригес - дрибес,
9
Одна из причин, по которой мне действительно понравился C #, когда он появился, заключалась в том, что он не отказался от всех прагматических функций C ++, как это сделала Java. Java пыталась делать все с интерфейсами, и это было катастрофой. Доказательство в пудинге. Со временем в Java было добавлено множество функций, которые изначально были объявлены ересью. Интерфейсный дизайн имеет свое место, но когда он доведен до крайности, он может излишне усложнить архитектуру программы. В итоге вы тратите больше времени на написание стандартного кода и меньше - на выполнение полезных вещей.
kgriffs
29

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

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

Сэм
источник
8
но неизменяемость действительно не масштабируется для сложных объектов, или да?
tenpn,
8
Я бы сказал, что если бы ваш объект был настолько сложным, что неизменяемость была бы невозможна, у вас был бы хороший кандидат для рефакторинга.
Джим Бургер,
2
Я считаю, что это лучший из группы. Неизменяемые объекты используются слишком редко.
3
Не только это, но есть случаи, когда вы действительно хотите иметь изменяющиеся объекты (объекты меняются!), Но в большинстве случаев все же предлагает представление только для чтения. Неизменяемые объекты подразумевают, что всякий раз, когда вам нужно внести изменение (изменение происходит), вам нужно будет создать новый объект со всеми теми же данными, кроме изменения. Представьте себе школу, в которой есть школьные комнаты, учащиеся ... вы хотите создавать новую школу каждый раз, когда проходит дата рождения учащегося и меняется ее возраст? Или вы можете просто изменить возраст на уровне ученика, ученика на уровне комнаты, может быть, комнаты на уровне школы?
Дэвид Родригес - дрибес
8
@tenpn: Неизменяемость на самом деле невероятно хорошо масштабируется, если все сделано правильно. Одна вещь, которая действительно помогает, - это использование Builderклассов для больших неизменяемых типов (Java и .NET определяют StringBuilderкласс, который является лишь одним примером).
Конрад Рудольф,
24

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

Рик Минерих
источник
4
По-прежнему существует проблема, заключающаяся в том, что Ts являются изменяемыми, даже если ReadOnlyCollection <T> нет.
arynaq
4

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

MSDN:

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

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

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

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

щедрый
источник
15
Не на 100% правильно. Константные методы C ++ могут изменять члены, помеченные как mutable.
Константин
3
Справедливо. А константное приведение позволяет избавиться от константности. И то, и другое подразумевает, что даже дизайнеры C ++ понимали, что «один размер подходит всем» на самом деле универсален.
Великолепный
8
Преимущество C ++ в том, что у вас есть const для большинства случаев, и вы можете реализовать интерфейсы для других. Теперь, помимо const, в C ++ есть ключевое слово mutable, которое применяется к атрибутам в качестве кэшей данных или механизмов блокировки (мьютексов и т.п.). Const is not 'не изменит никаких внутренних атрибутов), а скорее не изменит состояние объекта, воспринимаемое извне. То есть любое использование объекта до и после вызова метода const даст один и тот же результат.
Дэвид Родригес - дрибес
9
отказ от константы для изменения чего-то в C ++ - это поведение undefiend (носовые демоны). const_cast предназначен для взаимодействия с устаревшим кодом, который, будучи логически константным, не помечен как const.
jk.
3

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

    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; }
    }
  }
Стюарт МакКоннелл
источник
3
Это разные подходы, которые на самом деле не подходят для всей проблемы. С уровнем контроля доступа вы разрешаете только создание классов из изменений, во многих случаях это не будет соответствующим образом моделировать реальный мир. Учитель не является производным от записи ученика, но может захотеть изменить оценки ученика, хотя ученик не может изменять оценки, но может их читать ... просто чтобы назвать простой пример.
Дэвид Родригес - дрибес
2
  • СопзИте ключевое слово можно использовать для компиляции констант времени , таких как примитивные типов и строки
  • Только для чтения ключевого слова может быть использовано для констант времени выполнения , таких как ссылочные типы

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

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

Томас Братт
источник