Подтвердить что ты не робот

Как сделать свойство ссылочного типа "readonly"

У меня есть класс Bar с частным полем, содержащим ссылочный тип Foo. Я хотел бы открыть Foo в публичном свойстве, но я не хочу, чтобы потребители свойства могли изменять Foo... Однако он должен быть изменен внутри Bar, т.е. я не могу сделайте поле readonly.

Итак, я бы хотел:

      private _Foo;

      public Foo 
      { 
         get { return readonly _Foo; } 
      } 

... что, конечно, недействительно. Я мог бы просто вернуть клон Foo (при условии, что это IClonable), но это не очевидно для потребителя. Должен ли я изменить имя свойства на FooCopy? Должен ли он быть GetCopyOfFoo? Что вы считаете лучшей практикой? Спасибо!

4b9b3361

Ответ 1

Похоже, вы после эквивалента "const" из С++. Это не существует в С#. Нет никакого способа указать, что потребители не могут изменять свойства объекта, но что-то еще (при условии, что мутирующие члены общедоступны, конечно).

Вы можете вернуть клон Foo, как было предложено, или, возможно, представление на Foo, поскольку ReadOnlyCollection делает для коллекций. Конечно, если вы можете сделать Foo неизменяемым типом, это упростит жизнь...

Обратите внимание, что существует большая разница между созданием поля readonly и превращением объекта в неизменное.

В настоящее время сам тип может изменить ситуацию в обоих направлениях. Это может сделать:

_Foo = new Foo(...);

или

_Foo.SomeProperty = newValue;

Если это нужно только для того, чтобы сделать второе, поле может быть только для чтения, но у вас все еще есть проблема с тем, что люди получают свойство, которое может мутировать объект. Если это нужно только для первого, и на самом деле Foo является либо неизменным, либо может быть неизменным, вы можете просто предоставить свойство, которое имеет только "getter", и все будет в порядке.

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

Ответ 2

К сожалению, в настоящее время на С# нет простого пути. Вы можете извлечь "часть только для чтения" Foo в интерфейсе, и пусть ваше свойство вернет это вместо Foo.

Ответ 3

Создание копии, ReadOnlyCollection или наличие Foo неизменяемы, как правило, являются тремя лучшими маршрутами, как вы уже предполагали.

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

Ответ 4

"Клонирование" объектов Foo, которые вы получаете и возвращаете, является обычной практикой, называемой защитным копированием. Если не существует невидимого побочного эффекта для клонирования, который будет видимым для пользователя, нет абсолютно никаких оснований НЕ делать этого. Это часто единственный способ защитить внутренние личные данные ваших классов, особенно на С# или Java, где идея С++ const недоступна. (IE, это должно быть сделано, чтобы правильно создавать действительно неизменяемые объекты на этих двух языках.)

Чтобы уточнить, возможные побочные эффекты будут такими, как ваш пользователь (разумно), ожидая возвращения исходного объекта или какого-либо ресурса, который удерживает Foo, который не будет клонирован правильно. (В этом случае, что он делает, реализуя IClonable?!)

Ответ 5

Если вы не хотите, чтобы кто-то возился со своим государством... не выставляйте его! Как говорили другие, если что-то нужно для просмотра вашего внутреннего состояния, предоставьте ему неизменное представление. Кроме того, попросите клиентов сказать вам что-то сделать (Google для "tell not ask" ) вместо того, чтобы делать это самостоятельно.

Ответ 6

Чтобы прояснить комментарий Jon Skeet, вы можете сделать представление, которое является неизменным классом-оболочкой для изменяемого Foo. Вот пример:

class Foo{
 public string A{get; set;}
 public string B{get; set;}
 //...
}

class ReadOnlyFoo{
  Foo foo; 
  public string A { get { return foo.A; }}
  public string B { get { return foo.B; }}
}

Ответ 7

Фактически вы можете воспроизвести поведение С++ const в С# - вам просто нужно сделать это вручную.

Независимо от Foo, единственный способ, которым вызывающий может изменить свое состояние, - это вызвать методы на нем или установить свойства.

Например, Foo имеет тип FooClass:

class FooClass
{
    public void MutateMyStateYouBadBoy() { ... }

    public string Message
    {
        get { ... }
        set { ... }
    }
}

Итак, в вашем случае вы довольны тем, что получили свойство Message, но не задали его, и вам определенно не нравится, когда он вызывает этот метод.

Итак, определите интерфейс, описывающий, что им разрешено делать:

interface IFooConst
{
    public string Message
    {
        get { ... }
    }
}

Мы упустили метод мутирования и только оставили в getter свойство.

Затем добавьте этот интерфейс в базовый список FooClass.

Теперь в вашем классе с свойством Foo у вас есть поле:

private FooClass _foo;

И свойство getter:

public IFooConst Foo
{
    get { return _foo; }
}

Это в основном воспроизводит вручную именно то, что ключевое слово С++ const будет делать автоматически. В терминах psuedo-С++ ссылка типа const Foo & похожа на автоматически сгенерированный тип, который включает только те члены Foo, которые были помечены как const. Переведя это в некоторую теоретическую будущую версию С#, вы объявите FooClass следующим образом:

class FooClass
{
    public void MutateMyStateYouBadBoy() { ... }

    public string Message
    {
        get const { ... }
        set { ... }
    }
}

В действительности все, что я сделал, объединяет информацию в IFooConst обратно в FooClass, помещая один безопасный элемент новым ключевым словом const. Таким образом, добавление ключевого слова const не придавало бы большого значения языку помимо формального подхода к этому шаблону.

Тогда, если у вас есть ссылка const на объект FooClass:

const FooClass f = GetMeAFooClass();

Вы могли бы только вызвать члены const на f.

Обратите внимание, что если определение FooClass является общедоступным, вызывающий может передать IFooConst в FooClass. Но они тоже могут сделать это на С++ - он называется "отбрасывание const" и включает специальный оператор с именем const_cast<T>(const T &).

Там также проблема с интерфейсами, которые не очень легко развиваться между версиями вашего продукта. Если третья сторона может реализовать интерфейс, который вы определяете (который они могут делать, если они могут его видеть), то вы не можете добавлять к нему новые методы в будущих версиях, не требуя повторного компиляции своего кода. Но это только проблема, если вы пишете расширяемую библиотеку для других. Возможно, встроенная функция const разрешит эту проблему.

Ответ 8

Я думал о подобных вещах безопасности. Вероятно, есть путь. Довольно ясно, но не коротко. Общая идея довольно проста. Однако я всегда находил некоторые пути, поэтому никогда не тестировал его. Но вы можете проверить это - возможно, это сработает для вас.

Это псевдокод, но я надеюсь, что идея за ним понятна.

public delegate void OnlyRuller(string s1, string s2);
public delegate void RullerCoronation(OnlyRuller d);

class Foo {
  private Foo();
  public Foo(RullerCoronation followMyOrders) {
     followMyOrders(SetMe);
  }

  private SetMe(string whatToSet, string whitWhatValue) {
     //lot of unclear but private code
  }
}

Таким образом, в классе, который создает это свойство, у вас есть доступ к методу SetMe, но он по-прежнему частный, поэтому, кроме создателя, Foo выглядит немыслимым.

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

Однако, как я уже сказал, это только теория.