Как сделать свойство ссылочного типа «только для чтения» - PullRequest
11 голосов
/ 25 марта 2009

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

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

      private _Foo;

      public Foo 
      { 
         get { return readonly _Foo; } 
      } 

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

Ответы [ 8 ]

11 голосов
/ 25 марта 2009

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

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

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

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

_Foo = new Foo(...);

или

_Foo.SomeProperty = newValue;

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

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

3 голосов
/ 25 марта 2009

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

2 голосов
/ 25 марта 2009

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

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

1 голос
/ 25 марта 2009

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

1 голос
/ 25 марта 2009

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

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

0 голосов
/ 12 февраля 2015

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

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

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, это одна из альтернатив.

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

0 голосов
/ 25 марта 2009

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

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

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

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

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

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

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

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

Мы оставили метод мутации и оставили его только в получателе свойства.

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

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

private FooClass _foo;

И получатель недвижимости:

public IFooConst Foo
{
    get { return _foo; }
}

Это в основном воспроизводит вручную то, что ключевое слово C ++ const будет делать автоматически. В терминах psuedo-C ++ ссылка типа const Foo & подобна автоматически сгенерированному типу, который включает в себя только те элементы Foo, которые были отмечены как const члены. Если перевести это в некую теоретическую будущую версию C #, вы объявите 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. Но они могут делать это и в C ++ - это называется «отбрасывание const» и включает специальный оператор под названием const_cast<T>(const T &).

.

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

0 голосов
/ 25 марта 2009

Чтобы уточнить комментарий Джона Скита, вы можете создать представление, которое является неизменяемым классом-оберткой для изменяемого 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; }}
}
...