Как использовать C # только для чтения, но не ограничиваться конструктором? - PullRequest
5 голосов
/ 11 ноября 2011

Ключевое слово "readonly" в C # - это модификатор, который, когда объявление поля включает его, присваивания полям, введенным объявлением, может происходить только как часть объявления или в конструкторе в том же классе.

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

Как я мог это сделать? и можно ли сделать это хорошим способом, например, можно ли написать какой-нибудь атрибут, чтобы описать это?

Ответы [ 4 ]

6 голосов
/ 11 ноября 2011

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

public class SetOnce<T> 
{
    private T mySetOnceField;
    private bool isSet;

    // used to determine if the value for 
    // this SetOnce object has already been set.
    public bool IsSet
    {
      get { return isSet; }
    }
    // return true if this is the initial set, 
    // return false if this is after the initial set.
    // alternatively, you could make it be a void method
    // which would throw an exception upon any invocation after the first.
    public bool SetValue(T value)
    {
       // or you can make thread-safe with a lock..
       if (IsSet)
       {
          return false; // or throw exception.
       }
       else 
       {
          mySetOnceField = value;
          return isSet = true;
       }
    }

    public T GetValue()
    {
      // returns default value of T if not set. 
      // Or, check if not IsSet, throw exception.
      return mySetOnceField;         
    }
} // end SetOnce

public class MyClass 
{
  private SetOnce<int> myReadonlyField = new SetOnce<int>();
  public void DoSomething(int number)
  {
     // say this is where u want to FIRST set ur 'field'...
     // u could check if it's been set before by it's return value (or catching the exception).
     if (myReadOnlyField.SetValue(number))
     {
         // we just now initialized it for the first time...
         // u could use the value: int myNumber = myReadOnlyField.GetValue();
     }
     else
     {
       // field has already been set before...
     }

  } // end DoSomething

} // end MyClass
3 голосов
/ 11 ноября 2011

Теперь предположим, что я хочу это ограничение «назначить значение один раз», но я бы предпочел, чтобы назначение выполнялось вне конструкторов

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

Если вы хотите сделать это внутри класса

Вы можете использовать встроенные функции отложенной инициализации C # 4.0:

Или для более старых версий C # просто укажите метод get и проверьте, инициализированы ли вы уже с помощью вспомогательного поля:

public string SomeValue
{
    get
    {
        // Note: Not thread safe...
        if(someValue == null)
        {
            someValue = InitializeSomeValue(); // Todo: Implement
        }

        return someValue;
    }
}

Если вы хотите сделать этовне класса

Требуется неизменность эскимо:

В основном:

  • Вы делаете целые классы s доступно для записи, но добавьте метод Freeze.
  • После вызова этого метода заморозки, если пользователи пытаются вызвать методы установки или мутатора в вашем классе, вы бросаете ModifyFrozenObjectException.
  • Вы, вероятно, хотите, чтобы внешние классы могли определить, является ли ваш класс IsFrozen.

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

На данный момент я бы порекомендовал вам создать интерфейс IFreezable и, возможно, связанные исключения, так что вам не нужно зависетьна реализацию WPF.Что-то вроде:

public interface IFreezable
{
    void Freeze();
    bool IsFrozen { get; }
}
1 голос
/ 24 февраля 2012

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

public once Type PropertyName { get { /* generate and return value */ } }

в отличие от текущей лучшей практики ...

Type _PropertyName; //where type is a class or nullable structure
public Type PropertyName
{
    get
    {
        if (_PropertyName == null)
            _PropertyName = /* generate and return value */ 
        return _PropertyName
    }
}
1 голос
/ 11 ноября 2011

Вы можете использовать класс Lazy<T>:

private readonly Lazy<Foo> _foo = new Lazy<Foo>(GetFoo);

public Foo Foo
{
    get { return _foo.Value; }
}

private static Foo GetFoo()
{
    // somehow create a Foo...
}

GetFoo будет вызываться только при первом вызове свойства Foo.

...