Как я могу сделать версию класса только для чтения? - PullRequest
16 голосов
/ 28 апреля 2010

У меня есть класс с различными открытыми свойствами, которые я разрешаю пользователям редактировать через сетку свойств. В целях сохранения этот класс также сериализуется / десериализуется в / из файла XML через DataContractSerializer.

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

Мой класс имеет логическое свойство, которое определяет, должен ли класс быть доступным только для чтения, но возможно ли использовать это свойство для динамического добавления атрибутов только для чтения к свойствам класса во время выполнения? Если нет, что является альтернативным решением? Должен ли я обернуть свой класс в класс-обертку только для чтения?

Ответы [ 7 ]

19 голосов
/ 28 апреля 2010

Неизменяемость - это область, в которой C # все еще нуждается в улучшении. Хотя создание простых неизменяемых типов со свойствами readonly возможно, если вам требуется более сложный контроль над изменяемым типом, вы начинаете сталкиваться с препятствиями. .

У вас есть три варианта, в зависимости от того, насколько сильно вам нужно «применять» поведение только для чтения:

  1. Используйте флаг «только для чтения» в своем типе (как вы делаете), и пусть вызывающая сторона будет ответственна за то, что она не пытается изменить свойства типа - если сделана попытка записи, бросьте исключение.

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

  3. Создайте класс-оболочку, который агрегирует ваш тип и предоставляет только операции чтения.

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

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

Третий вариант является самым сильным с точки зрения принудительного применения, но он может привести к дублированию кода и требует больше усилий по рефакторингу. Часто полезно комбинировать опции 2 и 3, чтобы сделать связь между оболочкой, доступной только для чтения, и изменяемым типом полиморфной.

Лично я склоняюсь к третьему варианту при написании нового кода, в котором, как я ожидаю, потребуется обеспечить неизменность. Мне нравится тот факт, что невозможно «отбросить» неизменяемую оболочку, и она часто позволяет избежать записи грязных проверок if-only-only-throw-exception в каждый сеттер.

2 голосов
/ 23 февраля 2018

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

Библиотека утилит

public interface IReadOnlyClass
{
    string SomeProperty { get; }
    int Foo();
}
public interface IMutableClass
{
    string SomeProperty { set; }
    void Foo( int arg );
}

Ваша библиотека

internal MyReadOnlyClass : IReadOnlyClass, IMutableClass
{
    public string SomeProperty { get; set; }
    public int Foo()
    {
        return 4; // chosen by fair dice roll
                  // guaranteed to be random
    }
    public void Foo( int arg )
    {
        this.SomeProperty = arg.ToString();
    }
}
public SomeClass
{
    private MyThing = new MyReadOnlyClass();

    public IReadOnlyClass GetThing 
    { 
        get 
        { 
            return MyThing as IReadOnlyClass;
        }
    }
    public IMutableClass GetATotallyDifferentThing
    {
        get
        {
            return MyThing as IMutableClass
        }
    }
}

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

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

Кредит XKCD за комментарии.

2 голосов
/ 28 апреля 2010

Почему не что-то вроде:

private int someValue;
public int SomeValue
{
    get
    {
         return someValue;
    }
    set
    {
         if(ReadOnly)
              throw new InvalidOperationException("Object is readonly");
         someValue= value;
    }
1 голос
/ 28 апреля 2010

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

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

0 голосов
/ 28 апреля 2010

Вы можете использовать PostSharp для создания OnFieldAccessAspect, который не будет передавать новое значение в любое поле, когда _readOnly будет установлен в значение true. С повторением кода аспекта больше нет и поле не будет забыто.

0 голосов
/ 28 апреля 2010

Вы не можете получить проверки во время компиляции (например, с ключевым словом readonly), изменив свойство на readonly во время выполнения. Так что другого пути нет, как проверить вручную и сгенерировать исключение.

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

0 голосов
/ 28 апреля 2010

Хотелось бы что-то вроде этой помощи:

class Class1
{
    private bool _isReadOnly;

    private int _property1;
    public int Property1
    {
        get
        {
            return _property1;
        }
        set
        {
            if (_isReadOnly) 
              throw new Exception("At the moment this is ready only property.");
            _property1 = value;
        }
    }
}

Вам нужно перехватывать исключения при настройке свойств.

Надеюсь, это то, что вы ищете.

...