2010-04-27 2 views
13

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

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

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

ответ

16

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

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

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

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

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

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

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

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

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

+0

Спасибо! К сожалению, вариант 2 не работает для меня, потому что сетка свойств будет использовать отражение для определения реальных свойств интерфейса, на самом деле не только для чтения. Так что кажется, что вариант 3 - лучший ответ для меня. Хотя мне нравится ваше предложение объединить варианты 2 и 3. –

+0

Я не могу выразить, как этот ответ является одним из самых лаконичных и полных, которые я нашел в Интернете. –

1

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

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

2

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

private int someValue; 
public int SomeValue 
{ 
    get 
    { 
     return someValue; 
    } 
    set 
    { 
     if(ReadOnly) 
       throw new InvalidOperationException("Object is readonly"); 
     someValue= value; 
    } 
+1

Потому что он не будет работать (как и ожидалось) для ссылочных типов. Ваш код эквивалентен ключевому слову только для чтения, он более неясен. – greenoldman

+0

Зачем это эквивалентно ключевому слову readonly? Это динамическое, ключевое слово readonly - нет. –

0

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

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

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

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

+0

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

+0

@Oskar Правда, я изменил его. – user218447

0

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

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

0

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

1

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

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

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 для комментариев.

Смежные вопросы