Подтвердить что ты не робот

Более частный, чем частный? (С#)

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

4b9b3361

Ответ 1

IMHO, он не используется, потому что:

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

Ответ 2

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

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

Коды предупреждений, которые вам нужно подавить, CS0612 для простого атрибута Obsolete и CS0618, если атрибут имеет собственное сообщение.

[Obsolete("Please don't touch the backing field!")]
private int _backingField;

public int YourProperty
{
    #pragma warning disable 612, 618
    get { return _backingField; }
    set { _backingField = value; }
    #pragma warning restore 612, 618
}

Ответ 3

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

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

Ответ 4

Нет, нет. Мне это тоже понравилось - что-то вроде:

public string Name
{
    private string name; // Only accessible within the property
    get { return name; /* Extra processing here */ }
    set { name = value; /* Extra processing here */ }
}

Думаю, я впервые предложил это примерно 5 лет назад в новостных группах С#... Я не ожидаю, что когда-нибудь это произойдет.

Существуют различные морщины, которые следует учитывать при сериализации и т.д., но я все еще думаю, что было бы хорошо. Я бы предпочел автоматически реализовать свойства readonly, хотя...

Ответ 5

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

class MyClass {
  private Func<Foo> reallyPrivateFieldGetter;
  private Action<Foo> reallyPrivateFieldSetter;
  private Foo ReallyPrivateBackingFieldProperty {
    get { return reallyPrivateFieldGetter(); }
    set { reallyPrivateFieldSetter(value); }
  }

  public MyClass() {
    Foo reallyPrivateField = 0;
    reallyPrivateFieldGetter = () => { return reallyPrivateField; }
    reallyPrivateFieldSetter = v => { reallyPrivateField = v; };
  }
}

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

Ответ 6

В С# такого обеспечения нет.

Однако я бы назвал частные переменные по-разному (например, m_something или просто _something), поэтому его легче определить, когда он используется.

Ответ 7

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

public class A
{
   class FieldsForA
   {
      private int number;
      public int Number
      {
         get
         {
           //TODO: Extra logic.
           return number;
         }
         set
         {
           //TODO: Extra logic.
           number = value;
         }
      }
   } 
   FieldsForA fields = new FieldsForA();

   public int Number
   {
      get{ return fields.Number;}
      set{ fields.Number = value;}
   }       
}

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

Ответ 8

Возможно, хранилище поддержки свойств, похожее на способ WPF хранит свойства?

Итак, вы могли бы:

Dictionary<string,object> mPropertyBackingStore = new Dictionary<string,object> ();

public PropertyThing MyPropertyThing
{
    get { return mPropertyBackingStore["MyPropertyThing"] as PropertyThing; }
    set { mPropertyBackingStore["MyPropertyThing"] = value; }
}

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

P.S. Вы даже можете использовать инфраструктуру свойств зависимостей из WPF...
P.P.S. Это, очевидно, будет нести расходы на кастинг, но это зависит от ваших потребностей - если производительность критическая, возможно, это не решение для вас.
P.P.P.S Не забудьте инициализировать резервный магазин! (

EDIT:

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

Ответ 9

Невозможно сделать это в стандартном С#, однако вы могли бы

  • Определите пользовательский атрибут: OnlyAccessFromProperty

  • напишите свой код как

    [OnlyAccessFromProperty(Name)]
    String name
    
    Name 
    {
       get{return name;}
    }
    
    etc …
    
  • Затем напишите собственное правило для FxCop (или другой проверки)

  • Добавьте FxCop в свою систему сборки, чтобы, если ваше пользовательское правило обнаружило ошибку, сборка завершилась неудачей.

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

Ответ 10

У С# нет языковой функции для этого. Однако вы можете полагаться на соглашения об именах, аналогичные языкам, которые вообще не имеют личных свойств. Префикс более личных имен переменных _p_, и вы будете уверены, что не вводите его случайно.

Ответ 11

Я не знаю С#, но в Java у вас может быть базовый класс с только частными переменными экземпляра и публичными сеттерами и getters (должен возвращать копию экземпляра var.) и делать все остальное в унаследованном классе.

"Общий принцип проектирования" будет "использовать наследование".

Ответ 12

В С# нет встроенного решения, но я думаю, что ваша проблема может быть решена с помощью хорошего дизайна OO: Каждый класс должен иметь одну цель. Поэтому попробуйте извлечь логику вокруг своего поля в класс как можно меньше. Это уменьшает код, в котором вы можете получить доступ к полю случайно. Если вы делаете такие ошибки случайно, ваш класс, вероятно, большой.

Часто интерфейс хорош для ограничения доступа только к определенному "подмножеству" объекта. Если это подходит для вашего дела, это зависит от вашего курса. Более подробная информация о работе, которая должна быть выполнена, поможет обеспечить лучший ответ.

Ответ 13

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

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

Ответ 14

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

class MyInt
{
    private int n;

    public static implicit operator MyInt(int v) // Set
    {
        MyInt tmp = new MyInt();
        tmp.n = v;
        return tmp;
    }

    public static implicit operator int(MyInt v) // Get
    {
        return v.n;
    }
}

class MyClass
{
    private MyInt myint;

    public void func()
    {
        myint = 5;
        myint.n = 2; // Can't do this.
        myint = myint + 5 * 4; // Works just like an int.
    }
}

Я уверен, что я что-то упустил? Это кажется слишком нормальным...

Кстати, мне нравятся замыкания, великолепно безумные.

Ответ 15

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

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

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

Ответ 16

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

Ответ 17

Если вы используете компилятор С# 3.0, вы можете определить свойства, которые имеют поля для создания компилятора, такие как:

public int MyInt { get; set; }

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

Ответ 18

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

В Visual С#, EditorBrowsableAttribute не подавляет членов из класса в той же сборке.

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

public sealed class TriggerField<T>
{
    private T data;

    ///<summary>raised *after* the value changes, (old, new)</summary>
    public event Action<T,T> OnSet;

    public TriggerField() { }

    ///<summary>the initial value does NOT trigger the onSet</summary>
    public TriggerField(T initial) { this.data=initial; }

    public TriggerField(Action<T,T> onSet) { this.OnSet += onSet; }

    ///<summary>the initial value does NOT trigger the onSet</summary>
    public TriggerField(Action<T,T> onSet, T initial) : this(onSet) 
    { 
        this.data=initial;
    }

    public T Value
    {
        get { return this.data;}
        set 
        { 
            var old = this.data;
            this.data = value;
            if (this.OnSet != null)
                this.OnSet(old, value);
        }
    }
}

Позволяя вам (в некоторой степени verbosely) использовать его так:

public class Foo
{
    private readonly TriggerField<string> flibble = new TriggerField<string>();
    private int versionCount = 0;

    public Foo()
    {
        flibble.OnSet += (old,current) => this.versionCount++;  
    }

    public string Flibble 
    { 
        get { return this.flibble.Value; }
        set { this.flibble.Value = value; }
    }
}

в качестве альтернативы вы можете использовать менее подробный вариант, но доступ к Flibble осуществляется не идиоматическим bar.Flibble.Value = "x";, что было бы проблематичным в рефлексивных сценариях

public class Bar
{
    public readonly TriggerField<string> Flibble;
    private int versionCount = 0;

    public Bar()
    {
        Flibble = new TriggerField<string>((old,current) => this.versionCount++);
    }
}

  • или решение, если вы посмотрите на содержимое сообщества!

Ответ 19

Новый класс Lazy в .net 4.0

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

По моему опыту, это самая распространенная причина, по которой я хочу правильно поместить поле в частном порядке, поэтому хорошо разбирается в общем случае. (Если вы еще не используете .Net 4, вы можете просто создать свой собственный "Lazy" класс с тем же API, что и версия .Net 4.)

Смотрите this и this и this для получения подробной информации об использовании класса Lazy.

Ответ 20

Используйте тип конструкции "veryprivate"

Пример:

veryprivate void YourMethod()
{

  // code here
}