Лучший способ защитить вспомогательное поле от ошибочного использования в C # - PullRequest
4 голосов
/ 18 февраля 2009

У меня есть класс (Foo), который лениво загружает свойство с именем (Bar). Какой ваш предпочтительный способ защиты от ошибочного использования (из-за интеллигенции или неопытного персонала) поля неинициализированной поддержки?

Я могу придумать 3 варианта:

  class Foo {
    // option 1 - Easy to use this.bar by mistake. 
    string bar;
    string Bar {
        get {
            // logic to lazy load bar  
            return bar; 
        }
    }

    // option 2 - Harder to use this._bar by mistake. It is more obscure.
    string _bar2;
    string Bar2 {
        get {
            // logic to lazy load bar2  
            return _bar2;
        }
    }

    //option 3 - Very hard to use the backing field by mistake. 
    class BackingFields {
        public string bar; 
    }

    BackingFields fields = new BackingFields();

    string Bar3 {
        get {
            // logic to lazy load bar  
            return fields.bar;
        }
    }

}

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

Обновление

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

Примечание Моя реализация блокируется на чтение, поскольку она поддерживает внешний набор.

Кроме того, я хотел бы отметить, что я думаю, что это ограничение языка, которое можно преодолеть в Ruby, например .

Вы можете реализовать ленивый таким образом.

x = lazy do
    puts "<<< Evaluating lazy value >>>"
    "lazy value"
end

puts x
# <<< Evaluating lazy value >>>
# lazy value

Ответы [ 13 ]

0 голосов
/ 18 февраля 2009

Автоматические свойства:

public int PropertyName { get; set; }

запретит доступ к вспомогательному полю. Но если вы хотите поместить туда код (например, для отложенной загрузки при первом доступе), это явно не поможет.

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

например.

public class Foo {
  private class LazyLoader {
    private someType theValue;
    public someType Value {
      get {
        // Do lazy load
        return theValue;
      }
    }
  }

  private LazyLoader theValue;
  public someType {
    get { return theValue.Value; }
  }
}

Преимущество заключается в том, что вспомогательное поле труднее использовать, чем свойство.

(Еще один случай дополнительного уровня косвенности для решения проблем.)

0 голосов
/ 18 февраля 2009

Вариант 4 (новое решение) :

Посмотрите, действительно ли вопрос состоит в том, как запретить людям использовать неинициализированную переменную, а затем инициируйте ее со значением KNOWN INVALID.

Я бы сказал что-то вроде:

string str = "SOMETHING_WRONG_HERE";

У тех, кто когда-либо использует 'str', будет какое-то предупреждение.

В противном случае вариант 3, если предотвращение использования пользователем 'str' является более важным, чем удобочитаемость и т. Д.

0 голосов
/ 18 февраля 2009
// option 4
class Foo
{
    public int PublicProperty { get; set; }
    public int PrivateSetter { get; private set; }
}

C # 3.0, компилятор будет генерировать анонимные приватные поля поддержки, к которым нельзя получить доступ по ошибке, если вы не используете отражение ...

РЕДАКТИРОВАТЬ: Ленивый экземпляр

Вы можете иметь лень, как это:

// I changed this with respect to ShuggyCoUk's answer (Kudos!)
class LazyEval<T>
{
  T value;
  Func<T> eval;
  public LazyEval(Func<T> eval) { this.eval = eval; }
  public T Eval()
  {
      if (eval == null)
          return value;
      value = eval();
      eval = null;
      return value;
  }
  public static implicit operator T(LazyEval<T> lazy) // maybe explicit
  {
    return lazy.Eval();
  }
  public static implicit operator LazyEval<T>(Func<T> eval) 
  {
    return new LazyEval(eval);
  } 
}

Эти неявные преобразования делают синтаксис аккуратным ...

// option 5
class Foo
{
    public LazyEval<MyClass> LazyProperty { get; private set; }
    public Foo()
    {
        LazyProperty = () => new MyClass();
    }
}

И затворы могут использоваться для переноса объема:

// option 5
class Foo
{
    public int PublicProperty { get; private set; }
    public LazyEval<int> LazyProperty { get; private set; }
    public Foo()
    {
        LazyProperty = () => this.PublicProperty;
    }
    public void DoStuff()
    {
        var lazy = LazyProperty; // type is inferred as LazyEval`1, no eval
        PublicProperty = 7;
        int i = lazy; // same as lazy.Eval()
        Console.WriteLine(i); // WriteLine(7)
    }
}
...