Свойства, как правило, достаточно просты, но предлагаю сразу не переставать читать, а посмотреть на то, что будет подкатом.
А там будет общая информация о свойствах , но, самое главное будет о том, что делать, если свойство "медленное", т.е. на присвоение или чтение надо очень много времени.
Свойства
Итак, если вспомнить классику ООП, то класс объединяет данные и методы для их обработки.
Т.е. в самом простом случае, у нас может быть вот такая ситуация:
class Person
{
private string _lastName;
return _lastName;
}
_lastName = p_lastName;
}
}
Как видим все просто и эквивалентно вот такому варианту:
class Person
{
public string LastName;
}
Давайте посмотрим на варианты использования первого подхода:
List<Person> persons = new List<Person>();
Person person = new Person();
person.SetLastName("Иванов");
// ... некий код
int homonymCount = persons.Count(p => p.GetLastName() == person.GetLastName());
И второго:
List<Person> persons = new List<Person>();
Person person = new Person();
person.LastName = "Иванов";
// ... некий код
int homonymCount = persons.Count(p => p.LastName == person.LastName);
Как вам не знаю, а мне больше нравится второй способ записи. Зачем тогда городить огород?
А вот зачем, мы можем сделать вот так в первом случае:
class Person
{
private string _lastName;
public string GetLastName()
{
return _lastName;
}
public void SetLastName(string p_lastName)
{
if (!string.IsNullOrWhiteSpace(p_lastName))
{
_lastName = p_lastName;
}
else
{
throw new ArgumentException("Фамилия должна быть не null и содержать не только пробелы");
}
}
}
И, вполне понятно, что второй случай такого не обеспечивает.
Поэтому в современных языках программирования появился синтаксический сахар, который и получил название свойства.
Т.е. для использования по второму варианту, а реализации функционала по первому, можно использовать синтаксис:
class Person
{
private string _lastName;
public string LastName
{
get
{
return _lastName;
}
set
{
if (!string.IsNullOrWhiteSpace(value))
{
_lastName = value;
}
else
{
throw new ArgumentException("Фамилия должна быть не null и содержать не только пробелы");
}
}
}
}
Т.е. мы пишем и используем как нам удобно, а при компиляции все это разворачивается в те же методы чтения (аксессор) и изменения (мутатор). Именно это и было доступно во Framework 1.0. Со второго фреймворка стал доступен еще один вид синтаксического сахара. Т.к. зачастую свойство не несло дополнительной логики, то для сокращения записи применялся синтаксис:
class Person
{
public string LastName { get; set; }
}
Это все по прежнему на этапе компиляции разворачивалось в поле и два метода, но нам приходилось писать намного меньше. А наличие сниппета prop позволяет вообще набрать 4 символа и нажать Tab.
Не знаю как в WinForms, но в WPF весь Binding построен именно на свойствах, т.е. выполнить Binding к свойству - можно, к полю нет.
На этом со свойствами заканчиваю, единственно вот здесь можно посмотреть про DependencyProperty, если еще не в курсе что это такое и зачем оно нужно при биндинге.
Все, на этом экскурс заканчиваю, перехожу к теме.
Медленное чтение
Т.к. свойства это два метода, но благодаря тому, что они выглядят как поля в процессе использования, обычно практикуется подход, в котором свойства выполняются быстро. Т.е. вы пытаетесь считать и тут же получаете значение. Особенно это актуально при Binding-е т.к. если у вас на форме несколько свойств, которые выполняются медленно, то с перерисовкой могут возникнуть проблемы. Для примера, пусть у нас будет вот такой класс:
class Person
{
public string LastName
{
get
{
Thread.Sleep(2000);
return "Иванов";
}
}
get
{
return "Иван";
}
}
}
Здесь Thread.Sleep выполняет функцию имитации длительной операции подготовки данных.
Если создать окно вот с такой разметкой:
<Window x:Class="WpfApplication40.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
<Button Content="Set" Click="Button_Click" />
</StackPanel>
</Window>
И обработчиком клика по кнопке:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DataContext = new Person();
}
Запустив наше приложение и кликнув, мы увидим классический "зависший" интерфейс. Две секунды приложение не будет отвечать ни на какие события. Что делать? К сожалению универсальных решений не существует. Если у нас это только Binding, то мы можем воспользоваться параметром IsAsync, в этом случае сама среда выполнения будет выполнять получение значения в отдельном потоке и у нас все будет хорошо. Но что делать если у нас идет работа из кода? Если данные которое возвращает свойство можно кэшировать, то можно попробовать вот такой вариант:
private string _lastName = null;
get
{
if (_lastName == null)
{
Thread.Sleep(2000);
_lastName = "Иванов";
}
return _lastName;
}
}
Первый раз свойство работает медленно, потом быстро. Такое может пригодится, если мы работает с большим файлом, в котором сохранены некоторые данные. Первый раз обращаясь к свойству сколько наборов данных в файле, свойство "тормозит", т.к. разбирает файл, зато во всех остальных случаях работает быстро. Если у нас нет возможности тормозить основной поток даже при первом обращении или каждое чтение из нашего свойства приводит к длительной операции, то от такого свойства надо отказываться и переходить на async методы.
Медленная запись
Как я уже сказал выше, свойство должно быть быстром. Но что делать, если изменение свойства это медленная операция?
Давайте рассмотрим вот такой класс:
private string _lastName = null;
get
{
return _lastName;
}
set
{
if (value != _lastName)
{
_lastName = value;
SlowOperation(_lastName);
OnProeprtyChanged("LastName");
}
}
}
void SlowOperation(string p_value)
{
// Медленная операция с p_value
Thread.Sleep(2000);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnProeprtyChanged(string p_propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p_propertyName));
}
}
}
XAML пусть будет тот же, что и в примере выше, а вот код окна поменяем:
public MainWindow()
{
InitializeComponent();
person = new Person();
DataContext = person;
}
Person person = null;
private void Button_Click(object sender, RoutedEventArgs e)
{
person.LastName = "Иванов";
}
}
Запускаем приложение, нажимаем кнопку, наблюдаем "зависание", появляется надпись.
Решением в лоб, было бы сделать вот так:
public string LastName
{
get
{
return _lastName;
}
set
{
if (value != _lastName)
{
_lastName = value;
Task.Factory.StartNew(() => SlowOperation(_lastName));
OnProeprtyChanged("LastName");
}
}
}
Все, подвисание интерфейса исчезло, значение появляется мгновенно. Но, чтобы было понятно, к чему может привести такое решение "в лоб" давайте чуть усложним пример. Добавим в класс поле и нашу SlowOperation озадачим "реальной" работой с этим полем:
int i = 100000000;
while (i > 0)
{
_temp++;
_temp--;
i--;
}
}
Понятно, что если все выполняется нормально, то в поле _temp всегда будет 0. Но, давайте поравим код нашей кнопки, чтобы присвоения значения шли быстрее, чем успевает отработать SlowOperation:
for (int i = 0; i < 10; i++)
{
person.LastName = i.ToString();
}
}
Запускаем, нажимаем кнопку, немного ждем, в обработчик кнопки ставим точку останова (ну лень мне выводить поле), и смотри что у нас там в _temp (Можно кликнуть на картинке и посмотреть покрупнее):
От куда там взялось такое число? Да, это классические гонки.
Ок, дорабатываем наш код, чтобы у нас медленные операции выполнялись в отдельном потоке, но последовательно. В принципе, для этого мы можем обойтись простейшей блокировкой. Например, так:
object locker = new object();
void SlowOperation(string p_value)
{
lock(locker)
{
int i = 100000000;
while (i > 0)
{
_temp++;
_temp--;
i--;
}
}
}
Запускаем по уже описанной схеме:
Как видно, проблема гонок устранена.
А там будет общая информация о свойствах , но, самое главное будет о том, что делать, если свойство "медленное", т.е. на присвоение или чтение надо очень много времени.
Свойства
Итак, если вспомнить классику ООП, то класс объединяет данные и методы для их обработки.
Т.е. в самом простом случае, у нас может быть вот такая ситуация:
class Person
{
private string _lastName;
public string GetLastName()
{return _lastName;
}
public void SetLastName(string
p_lastName)
{_lastName = p_lastName;
}
}
Как видим все просто и эквивалентно вот такому варианту:
class Person
{
public string LastName;
}
Давайте посмотрим на варианты использования первого подхода:
List<Person> persons = new List<Person>();
Person person = new Person();
person.SetLastName("Иванов");
// ... некий код
int homonymCount = persons.Count(p => p.GetLastName() == person.GetLastName());
И второго:
List<Person> persons = new List<Person>();
Person person = new Person();
person.LastName = "Иванов";
// ... некий код
int homonymCount = persons.Count(p => p.LastName == person.LastName);
Как вам не знаю, а мне больше нравится второй способ записи. Зачем тогда городить огород?
А вот зачем, мы можем сделать вот так в первом случае:
class Person
{
private string _lastName;
public string GetLastName()
{
return _lastName;
}
public void SetLastName(string p_lastName)
{
if (!string.IsNullOrWhiteSpace(p_lastName))
{
_lastName = p_lastName;
}
else
{
throw new ArgumentException("Фамилия должна быть не null и содержать не только пробелы");
}
}
}
И, вполне понятно, что второй случай такого не обеспечивает.
Поэтому в современных языках программирования появился синтаксический сахар, который и получил название свойства.
Т.е. для использования по второму варианту, а реализации функционала по первому, можно использовать синтаксис:
class Person
{
private string _lastName;
public string LastName
{
get
{
return _lastName;
}
set
{
if (!string.IsNullOrWhiteSpace(value))
{
_lastName = value;
}
else
{
throw new ArgumentException("Фамилия должна быть не null и содержать не только пробелы");
}
}
}
}
Т.е. мы пишем и используем как нам удобно, а при компиляции все это разворачивается в те же методы чтения (аксессор) и изменения (мутатор). Именно это и было доступно во Framework 1.0. Со второго фреймворка стал доступен еще один вид синтаксического сахара. Т.к. зачастую свойство не несло дополнительной логики, то для сокращения записи применялся синтаксис:
class Person
{
public string LastName { get; set; }
}
Это все по прежнему на этапе компиляции разворачивалось в поле и два метода, но нам приходилось писать намного меньше. А наличие сниппета prop позволяет вообще набрать 4 символа и нажать Tab.
Не знаю как в WinForms, но в WPF весь Binding построен именно на свойствах, т.е. выполнить Binding к свойству - можно, к полю нет.
На этом со свойствами заканчиваю, единственно вот здесь можно посмотреть про DependencyProperty, если еще не в курсе что это такое и зачем оно нужно при биндинге.
Все, на этом экскурс заканчиваю, перехожу к теме.
Медленное чтение
Т.к. свойства это два метода, но благодаря тому, что они выглядят как поля в процессе использования, обычно практикуется подход, в котором свойства выполняются быстро. Т.е. вы пытаетесь считать и тут же получаете значение. Особенно это актуально при Binding-е т.к. если у вас на форме несколько свойств, которые выполняются медленно, то с перерисовкой могут возникнуть проблемы. Для примера, пусть у нас будет вот такой класс:
class Person
{
public string LastName
{
get
{
Thread.Sleep(2000);
return "Иванов";
}
}
public string FirstName
{get
{
return "Иван";
}
}
}
Здесь Thread.Sleep выполняет функцию имитации длительной операции подготовки данных.
Если создать окно вот с такой разметкой:
<Window x:Class="WpfApplication40.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
<Button Content="Set" Click="Button_Click" />
</StackPanel>
</Window>
И обработчиком клика по кнопке:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DataContext = new Person();
}
Запустив наше приложение и кликнув, мы увидим классический "зависший" интерфейс. Две секунды приложение не будет отвечать ни на какие события. Что делать? К сожалению универсальных решений не существует. Если у нас это только Binding, то мы можем воспользоваться параметром IsAsync, в этом случае сама среда выполнения будет выполнять получение значения в отдельном потоке и у нас все будет хорошо. Но что делать если у нас идет работа из кода? Если данные которое возвращает свойство можно кэшировать, то можно попробовать вот такой вариант:
private string _lastName = null;
public
string LastName
{get
{
if (_lastName == null)
{
Thread.Sleep(2000);
_lastName = "Иванов";
}
return _lastName;
}
}
Первый раз свойство работает медленно, потом быстро. Такое может пригодится, если мы работает с большим файлом, в котором сохранены некоторые данные. Первый раз обращаясь к свойству сколько наборов данных в файле, свойство "тормозит", т.к. разбирает файл, зато во всех остальных случаях работает быстро. Если у нас нет возможности тормозить основной поток даже при первом обращении или каждое чтение из нашего свойства приводит к длительной операции, то от такого свойства надо отказываться и переходить на async методы.
Медленная запись
Как я уже сказал выше, свойство должно быть быстром. Но что делать, если изменение свойства это медленная операция?
Давайте рассмотрим вот такой класс:
class
Person : INotifyPropertyChanged
{private string _lastName = null;
public string LastName
{get
{
return _lastName;
}
set
{
if (value != _lastName)
{
_lastName = value;
SlowOperation(_lastName);
OnProeprtyChanged("LastName");
}
}
}
void SlowOperation(string p_value)
{
// Медленная операция с p_value
Thread.Sleep(2000);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnProeprtyChanged(string p_propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p_propertyName));
}
}
}
XAML пусть будет тот же, что и в примере выше, а вот код окна поменяем:
public
partial class MainWindow : Window
{public MainWindow()
{
InitializeComponent();
person = new Person();
DataContext = person;
}
Person person = null;
private void Button_Click(object sender, RoutedEventArgs e)
{
person.LastName = "Иванов";
}
}
Запускаем приложение, нажимаем кнопку, наблюдаем "зависание", появляется надпись.
Решением в лоб, было бы сделать вот так:
public string LastName
{
get
{
return _lastName;
}
set
{
if (value != _lastName)
{
_lastName = value;
Task.Factory.StartNew(() => SlowOperation(_lastName));
OnProeprtyChanged("LastName");
}
}
}
Все, подвисание интерфейса исчезло, значение появляется мгновенно. Но, чтобы было понятно, к чему может привести такое решение "в лоб" давайте чуть усложним пример. Добавим в класс поле и нашу SlowOperation озадачим "реальной" работой с этим полем:
int
_temp = 0;
void
SlowOperation(string p_value)
{int i = 100000000;
while (i > 0)
{
_temp++;
_temp--;
i--;
}
}
Понятно, что если все выполняется нормально, то в поле _temp всегда будет 0. Но, давайте поравим код нашей кнопки, чтобы присвоения значения шли быстрее, чем успевает отработать SlowOperation:
private
void Button_Click(object
sender, RoutedEventArgs e)
{for (int i = 0; i < 10; i++)
{
person.LastName = i.ToString();
}
}
Запускаем, нажимаем кнопку, немного ждем, в обработчик кнопки ставим точку останова (ну лень мне выводить поле), и смотри что у нас там в _temp (Можно кликнуть на картинке и посмотреть покрупнее):
От куда там взялось такое число? Да, это классические гонки.
Ок, дорабатываем наш код, чтобы у нас медленные операции выполнялись в отдельном потоке, но последовательно. В принципе, для этого мы можем обойтись простейшей блокировкой. Например, так:
int
_temp = 0;
object locker = new object();
void SlowOperation(string p_value)
{
lock(locker)
{
int i = 100000000;
while (i > 0)
{
_temp++;
_temp--;
i--;
}
}
}
Запускаем по уже описанной схеме:
Как видно, проблема гонок устранена.
Комментариев нет:
Отправить комментарий