Ответы пользователя по тегу Паттерны проектирования
  • Книги по C# с паттернами и примерами архитектуры?

    Casper-SC
    @Casper-SC
    Программист (.NET)
    Принципы, паттерны и методики гибкой разработки на языке C# | Мартин Роберт С., Мартин Мика (ссылка)
    Ответ написан
    6 комментариев
  • Как правильно реализовать информирование ViewModel'и по MVVM?

    Casper-SC
    @Casper-SC
    Программист (.NET)
    Читай комментарии в коде, в том числе зачем там lock. Вдруг ты будешь использовать такой поиск с отменой не в UI потоке (не именно этот код, а саму логику).

    Models/SearchAlgorithm.cs
    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);
                }
            }
        }
    }


    ViewModels/MainViewModel.cs
    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;
            }
        }
    }


    Затестил я по быстрому так. Это тебе не особо нужно:

    MainWindow.xaml.cs
    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>
    Ответ написан
    1 комментарий
  • Как понять что в объект была добавлена информация, но не изменена существующая?

    Casper-SC
    @Casper-SC
    Программист (.NET)
    Если нужно бросать исключение (я бы бросал). С массивами ты ничего не сделаешь, чтобы нельзя было менять часть массива. Нужно использовать свой тип данных вместо массива, в котором и делать проверки.
    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};");
            }
        }
    }
    Ответ написан
    Комментировать