Лучший способ защитить вспомогательное поле от ошибочного использования в C#

У меня есть класс (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;
        }
    }

}

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

Обновить

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

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

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

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

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

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

14 ответов

Решение

Вариант 5

Lazy<T>

Работает довольно хорошо в нескольких ситуациях, хотя вариант 1 действительно должен подойти для большинства проектов, если разработчики не идиоты.

Добавление [EditorBrowsable(EditorBrowsableState.Never)] к полю не поможет, если оно является закрытым, поскольку эта логика включает только intellisense, сгенерированный из метаданных, а не из текущего кода (текущий проект и все, что делается с помощью ссылок на проекты, а не dll).

Замечания: Lazy<T> не является потокобезопасным (это хорошо, нет смысла блокировать, если вам это не нужно), если вам требуется поточная безопасность, либо используйте один из потоковобезопасных из Joe Duffy или Parallel Exetensions CTP

Как насчет использования ObsoleteAttribute а также #pragma - Трудно пропустить это тогда!

    void Test1()
    {
        _prop = ""; // warning given
    }
    public string Prop
    {
#pragma warning disable 0618
        get { return _prop; }
        set { _prop = value; }
#pragma warning restore 0618
    }
    [Obsolete("This is the backing field for lazy data; do not use!!")]
    private string _prop;
    void Test2()
    {
        _prop = ""; // warning given
    }

Я обычно выбираю вариант 2, так как позже ошибки легче обнаружить, хотя вариант 1 пройдет проверку кода. Вариант 3 кажется запутанным, и хотя он может работать, не будет хорошим кодом пересмотреть 6 месяцев подряд, пытаясь реорганизовать / исправить ошибку / и т.д.

Обзоры кода поймают на неправильном использовании, поэтому просто перейдите к наиболее читабельным. Мне не нравятся попытки обойти плохих программистов в коде, потому что 1) они не работают, 2) они усложняют работу умных программистов, и 3) они обращаются к симптому, а не к причине проблемы.

Вариант 1 в сочетании с некоторым образованием.

Обоснование: программное обеспечение предназначено для чтения чаще, чем написано, поэтому оптимизируйте его для общего случая и сохраняйте его читабельным.

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

Не было бы замечательно, если бы следующая версия C# позволяла что-то вроде этого:

public class MyClass
{
    public int MyProperty
    {
        int _backingField;

        get
        {
            return _backingField;
        }
        set
        {
            if( _backingField != value )
            {
                _backingField = value;
                // Additional logic
                ...
            }
        }
    }
}

С такой конструкцией _backingField Область действия переменной ограничена свойством. Я хотел бы видеть похожую языковую конструкцию в следующей версии C#:)

Но, боюсь, эта функция никогда не будет реализована: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=381625

Я обычно просто выбираю вариант 1. Поскольку это частное поле, я не думаю, что это действительно проблема, и использование чего-то вроде класса-оболочки, как в вашем варианте 3, только затрудняет чтение и понимание кода.

Я бы просто поместил большой блок комментариев в верхней части класса, который бы выглядел так:

/************************************************************
* Note: When updating this class, please take care of using *
*       only the accessors to access member data because of *
*       ... (state the reasons / your name, so they can ask *
*       questions)                                          *
*************************************************************/

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

Вариант 6:

Делает это действительно очень глупым, если вы его используете.

      string doNotUseThisBackingField_bar6;
string Bar6 {
   get {
      // logic to lazy load
      return doNotUseThisBackingField_bar6;
   }
}

Это может быть слишком просто, но почему бы не перевести все ленивые в базовый класс

 public class LazyFoo{
      private string bar;
      public string Bar{
         get{
             // lazy load and return
         }
         set {
             // set
         }
      }
    }

    public class Foo : LazyFoo{
      // only access the public properties here
    }

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

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

Я хотел бы пойти с вариантом 1 + комментарии:

///<summary>use Bar property instead</summary> 
string bar;

///<summary>Lazy gets the value of Bar and stores it in bar</summary>
string Bar {
    get {
        // logic to lazy load bar  
        return bar; 
    }
}

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

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

И если вы все еще действительно беспокоитесь об этом, создайте правило FxCop (или что-то еще, что вы используете), чтобы проверять подобные вещи.

// 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)
    }
}

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

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; }
  }
}

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

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

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

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

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

string str = "SOMETHING_WRONG_HERE";

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

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

Другие вопросы по тегам