Как создать свойство ссылочного типа “только для чтения”

StackOverflow https://stackoverflow.com/questions/681287

Вопрос

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

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

      private _Foo;

      public Foo 
      { 
         get { return readonly _Foo; } 
      } 

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

Это было полезно?

Решение

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

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

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

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

_Foo = new Foo(...);

или

_Foo.SomeProperty = newValue;

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

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

Другие советы

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

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

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

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

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

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

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

Вы действительно можете воспроизвести поведение 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 ключевое слово будет сделано автоматически.В терминах псевдо-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();

Вы могли бы вызывать постоянных участников только на f.

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

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

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

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

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, но он по-прежнему закрытый, поэтому, за исключением creator Foo, выглядит неизменяемым.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top