Как запретить использование полей вместо свойств?

У меня есть свойство в моем классе, у которого много логики в set:

private String text;

public String Text
{
   get { return text; }
   private set
   {
      // some actions with a value
      value = value.Replace('a', 'b');

      text = value;
   }
}

Как я могу запретить другим разработчикам (или даже мне) изменять поле вместо свойства внутри этого класса?

Если кто-то пишет что-то вроде этого:

public Test(String text)
{
   this.text = text;
}

он нарушит логику моего класса!

Ответ 1

Обычно классы должны быть достаточно маленькими, чтобы это не было проблемой. Поскольку поле private, только код внутри того же типа будет иметь доступ к полю. Однако, если вам нужно подчеркнуть важность, вы можете сделать:

[Obsolete("Use the Text property, or I break your legs; fair warning")]
private string text;

public string Text
{
#pragma warning disable 0618
    get { return text; }
    private set
    {
        // some actions with a value
        value = value.Replace('a', 'b');

        text = value;
    }
#pragma warning restore 0618
}

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

Ответ 2

Существует правильный способ сделать это. Рассмотрим старую пословицу "Все проблемы в информатике могут быть решены другим уровнем косвенности".

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

class TextSetter
{
    private string text;
    public TextSetter(string text)
    {
        Text = text;
    }
    public string Text
    {
        get{ return text;}
        set{ text = value.Replace('a', 'b');}
    }
}

Тогда в вашем первом классе вместо private string text; у вас есть private TextSetter text; теперь нет никаких шансов, что кто-то случайно установит значение напрямую.

вы могли бы даже обобщить это, если это общая проблема

class FieldSetter<T>
{
    private T field;
    private Func<T, T> setter;
    public FieldSetter(Func<T, T> setter, T field)
    {
        this.setter = setter
        Value = field;
    }
    public T Value
    {
        get{ return field;}
        set{ field = setter(value);}
    }
}

Не забудьте переименовать, как вам удобно.

Ответ 3

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

Ответ 4

В качестве альтернативного подхода, основываясь на обсуждении с Binary Worrier (комментарии), вы можете сделать что-то вроде:

internal struct SanitizedText {
    private readonly string value;
    private SanitizedText(string value) {
        this.value = value;
    }
    public static implicit operator string (SanitizedText value) {
        return value.value;
    }
    public static implicit operator SanitizedText(string value) {
        if (value != null) value = value.Replace('a', 'b');
        return new SanitizedText(value);
    }
}

а затем просто:

private SanitizedText text;
public string Text {
    get { return text; }
    set { text = value; }
}

Это делает все те же проверки, но нельзя злоупотреблять: вы не можете создать значение SanitizedText, которое обходит проверку; в равной степени он не занимает больше места (размер struct - это размер содержимого: 1 ссылка) и не требует выделения. Он также по умолчанию имеет строку null таким же образом.

Ответ 5

Вы можете использовать коллекции (по одному для каждого типа) в качестве "хранилища резервных копий":

private static readonly Dictionary<string, int> __intFields = new Dictionary<string, int>();
private static readonly Dictionary<string, string> __stringFields = new Dictionary<string, string>();

public static string StringProp
{
    get
    { 
        var fieldValue = default(string);
        __stringFields .TryGetValue("StringProp", out fieldValue);
        return fieldValue;
    }
    set
    {
        var manipulatedValue = applyOtherCode(value); // the rest of the code
        __stringFields ["StringProp"] = manipulatedValue
    }
}

public static int IntProp
{
    get
    { 
        var fieldValue = default(int);
        __intFields .TryGetValue("IntProp", out fieldValue);
        return fieldValue;
    }
    set
    {
        var manipulatedValue = applyOtherCode(value); // the rest of the code
        __intFields ["IntProp"] = manipulatedValue
    }
}
// There is no field for anyone to mistakenly access!

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

У меня первоначально была одна коллекция <string, object>... но я ее ненавидел. Один из типов позволяет избежать кастования данных все время