using System;
using System.Threading;
using System.Threading.Tasks;
namespace EventsInModel.Models
{
public class SearchAlgorithm
{
public string CurrentFolder { get; private set; }
public event EventHandler ProgressChanged;
public async Task Search(CancellationToken cancellationToken)
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1200, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
// Можно прогресс передавать и в качестве аргумента события,
// но в данном случае, вряд ли это оправдано. Обработав событие можно получить
// доступ к отправителю события и прочитать его свойства.
CurrentFolder = i.ToString();
ProgressChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}
using System;
using System.Threading;
using System.Windows.Input;
using EventsInModel.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
namespace EventsInModel.ViewModels
{
// ViewModelBase из библиотеки MvvmLight
public class MainViewModel : ViewModelBase
{
//private readonly object _sync = new object();
private readonly SearchAlgorithm _search;
private string _currentFolder;
// Логику с отменой можно вынести в отдельный класс, чтобы не писать простыню
// с отменой в каждом таком месте с операцией, которая может быть отменена, а
// в UI приложениях такое сплошь и рядом.
private volatile CancellationTokenSource _lastCancellationTokenSource;
public string CurrentFolder
{
get { return _currentFolder; }
private set { Set(ref _currentFolder, value); }
}
public ICommand SearchCommand { get; }
public MainViewModel(SearchAlgorithm search)
{
_search = search;
_search.ProgressChanged += OnSearchProgressChanged;
SearchCommand = new RelayCommand(Search);
}
public override void Cleanup()
{
//lock (_sync)
{
_lastCancellationTokenSource?.Cancel();
}
_search.ProgressChanged -= OnSearchProgressChanged;
base.Cleanup();
}
/// <summary>
/// Прерывает прошлый поиск и запускает новый.
/// </summary>
private async void Search()
{
CancellationTokenSource currentTokenSource;
// В случае, если такой метод вызывать не из UI потока, то lock здесь нужен
// Если использовать только из UI потока как здесь, то lock можно удалить.
// Ещё бы я вынес логику в отдельный класс и использовал в других проектах в том числе.
//lock (_sync)
{
_lastCancellationTokenSource?.Cancel();
currentTokenSource = new CancellationTokenSource();
_lastCancellationTokenSource = currentTokenSource;
}
try
{
await _search.Search(currentTokenSource.Token);
}
catch (OperationCanceledException)
{
// Ignored.
}
finally
{
//lock (_sync)
{
currentTokenSource.Dispose();
if (ReferenceEquals(_lastCancellationTokenSource, currentTokenSource))
{
_lastCancellationTokenSource = null;
}
}
}
}
private void OnSearchProgressChanged(object sender, EventArgs e)
{
var search = (SearchAlgorithm)sender;
CurrentFolder = search.CurrentFolder;
}
}
}
using System.Windows;
using EventsInModel.Models;
using EventsInModel.ViewModels;
namespace EventsInModel
{
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel(new SearchAlgorithm());
DataContext = _viewModel;
Loaded += OnLoaded;
Closing += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_viewModel.SearchCommand.Execute(null);
_viewModel.SearchCommand.Execute(null);
}
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
_viewModel.Cleanup();
}
}
}
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MvvmLightLibsStd10" Version="5.4.1.1" />
</ItemGroup>
</Project>
using System;
namespace ConsoleApp1
{
public class ModelBase
{
protected bool CheckValueTypeField<T>(T field)
where T : struct
{
return field.Equals(default(T));
}
protected bool CheckReferenceTypeField<T>(T field)
where T : class
{
return field == null;
}
protected void ThrowIfNotDefault<T>(T field)
where T : struct
{
if (!CheckValueTypeField(field))
{
throw new InvalidOperationException();
}
}
protected void ThrowIfNotNull<T>(T field)
where T : class
{
if (!CheckReferenceTypeField(field))
{
throw new InvalidOperationException();
}
}
}
public class TestModel : ModelBase
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set
{
ThrowIfNotNull(_name);
_name = value;
}
}
public int Value
{
get { return _value; }
set
{
ThrowIfNotDefault(_value);
_value = value;
}
}
}
class Program
{
static void Main(string[] args)
{
var test = new TestModel();
test.Name = "test name";
test.Value = 1;
try
{
test.Name = "new name";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
test.Value = 5;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Display(test);
}
static void Display(TestModel model)
{
Console.WriteLine(
$"{nameof(model.Name)} == {model.Name}; {nameof(model.Value)} == {model.Value};");
}
}
}
public class TestModel : ModelBase
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set
{
if (!CheckReferenceTypeField(_name))
{
_name = value;
}
}
}
public int Value
{
get { return _value; }
set
{
if (!CheckValueTypeField(_value))
{
_value = value;
}
}
}
}
using System;
namespace ConsoleApp1
{
public abstract class ModelBase
{
private readonly bool _throwAnExceptionInsteadOfAnEvent;
public event EventHandler FieldIsNotDefault;
protected ModelBase(bool throwAnExceptionInsteadOfAnEvent = true)
{
_throwAnExceptionInsteadOfAnEvent = throwAnExceptionInsteadOfAnEvent;
}
protected bool CheckValueTypeField<T>(T field)
where T : struct
{
return field.Equals(default(T));
}
protected bool CheckReferenceTypeField<T>(T field)
where T : class
{
return field == null;
}
protected void AssertIsNotDefault<T>(T field)
where T : struct
{
if (!CheckValueTypeField(field))
{
if (_throwAnExceptionInsteadOfAnEvent)
{
throw new InvalidOperationException();
}
OnFieldIsNotDefault();
}
}
protected void AssertIsNotNull<T>(T field)
where T : class
{
if (!CheckReferenceTypeField(field))
{
if (_throwAnExceptionInsteadOfAnEvent)
{
throw new InvalidOperationException();
}
OnFieldIsNotDefault();
}
}
protected void OnFieldIsNotDefault()
{
FieldIsNotDefault?.Invoke(this, EventArgs.Empty);
}
}
public class TestModel : ModelBase
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set
{
AssertIsNotNull(_name);
_name = value;
}
}
public int Value
{
get { return _value; }
set
{
AssertIsNotDefault(_value);
_value = value;
}
}
public TestModel()
: base(false)
{
}
}
class Program
{
static void Main(string[] args)
{
var test = new TestModel();
test.FieldIsNotDefault += OnFieldIsNotDefault;
test.Name = "test name";
test.Value = 1;
try
{
test.Name = "new name";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
test.Value = 5;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
test.FieldIsNotDefault -= OnFieldIsNotDefault;
Display(test);
}
private static void OnFieldIsNotDefault(object sender, EventArgs e)
{
Console.WriteLine("Поле уже установлено");
}
static void Display(TestModel model)
{
Console.WriteLine(
$"{nameof(model.Name)} == {model.Name}; {nameof(model.Value)} == {model.Value};");
}
}
}