Создание динамического прокси-объекта с помощью dynamic типа

Как и многие люди, перед которыми стоит задача написания очередного UI для своего приложения, я периодически сталкиваюсь с необходимостью создания для UI своей собственной модели, которая в какой-то мере повторяет модель предметной области, однако при этом расширяем и/или изменяет ее. И вот что из этого вышло.

Постановка задачи


Например, у нас есть класс Region такой структуры
  1. public class Region
  2. {
  3.   public string Name { get; set; }
  4.   public int Index { get; set; }
  5.   public IEnumerable<Region> SubRegions { get; set; }
  6. }
* This source code was highlighted with Source Code Highlighter.

И есть задача показать пользователю в UI дерево регионов, построенное по этой модели, а так же дать возможность выбрать определенные регионы, которые подлежат какой-то дальнейшей обработке. Если мы делаем наш UI по MVVM архитектуре, то очевидно, что нам понадобится создать класс типа RegionViewModel, со структурой, аналогичной классу Region, но имеющей дополнительное свойство IsSelected. К тому же было бы удобно, если бы у RegionViewModel свойство SubRegions возращало энумератор типа IEnumerable<RegionViewModel>, тогда весь код UI свелся бы к созданию байндига у TreeView на список RegionViewModel и функции получения всех RegionViewModel, у которых свойство IsSelected равно true. А в чем же проблема? Проблема в том, что большая часть кода класса RegionViewModel будет представлять собой проброс данных в класс Region, который он и оборачивает. Возможна, конечно, такая реализация, несколько упрощающая жизнь:
  1. public class RegionViewModel
  2. {
  3.   public Region Value { get; set; }
  4.   public IEnumerable<RegionViewModel> SubRegions { get {/*...*/ }  }
  5. }
* This source code was highlighted with Source Code Highlighter.

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

Возможные решения


Если приглядеться, то становится видно, что наша задача сводиться к созданию некого прокси-генератора, который создает прокси для заданного класса и дополняет его какими-то аспектами, типа INotifyPropertyChanged, или/и добавляет новые свойства, методы и т.д. Что нам предлагает .net стек для создания проксей?

Можно выделить следующие решения
  • DynamicProxy от Castle
  • RealProxy от MS
  • кодогенерацию на базе T4
  • создать что-нибудь на базе инструментирования кода с помощью PostSharp.
  • Использовать появившийся в C# 4.0 dynamic

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

Реализация


И так, основная идея заключает в следующем – описываем класс, который наследуется от типа DynamicObject и переопределяет TryInvokeMember, TrySetMember и TryGetMember.
Например
  1. public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
  2.   {
  3.     result = _methods.ContainsKey(binder.Name) ? _methods[binder.Name].DynamicInvoke(new[] { this }.Concat(args).ToArray()) : InvokeNativeMethod(binder.Name,args);
  4.  
  5.     result = GetResult(result);
  6.  
  7.     return true;
  8.   }
* This source code was highlighted with Source Code Highlighter.

Делам так, чтобы он принимал на входе нужный объект, а при попытке вызова какого-то члена класса, прокидывал вызов в этот объект. Добавляем к этому возможность добавление новых методов и свойство. Для построение объекта будем использовать патер Fluent builder.

Сам код прокси-класс получился довольно большим(порядка 280 строчек), поэтому желающие его увидеть могут скачать исходники и все примеры по ссылке внизу поста. Здесь же я приведу примеры использования.

Добавление свойств


  1. private class MyClass
  2. {
  3.   MyClass _i;
  4.     
  5.   public string Name { get; set; }
  6.   public MyClass Foo()
  7.   {
  8.   return _i ?? (_i = new MyClass{Name = "_sdfdsfsdfsfd"});
  9.   }
  10.  
  11.   public IEnumerable<MyClass> GetChilds()
  12.   {
  13.     yield return new MyClass();
  14.     yield return new MyClass();
  15.   }
  16. }
  17.  
  18. [TestMethod]
  19. public void TestAddProperties()
  20. {
  21.   var a = new MyClass{Name="123"};
  22.  
  23.   Assert.AreEqual("123",a.Name);
  24.   dynamic proxy = DynamicProxy.Create(a).AddProperty<bool>("IsSelected")             
  25.                                         .AddProperty("X", _ => x, (_, value) => x = value)              
  26.                                         .AddProperty("LastName","FFFF")
  27.                                           
  28.  
  29.   proxy.Name = "567";
  30.   proxy.IsSelected = true;
  31.   proxy.X = 42;
  32.  
  33.   Assert.AreEqual("567",a.Name);
  34.   Assert.IsTrue(proxy.IsSelected);
  35.   Assert.AreEqual(42, x);
  36.  
  37.   proxy.IsSelected = false;
  38.  
  39.   Assert.IsFalse(proxy.IsSelected);
  40. }
* This source code was highlighted with Source Code Highlighter.

Автосоздание прокси для значений функций и свойств


  1. [TestMethod]
  2. public void TestChilds()
  3. {
  4.   var a = new MyClass{Name="123"};
  5.  
  6.   Assert.AreEqual("123",a.Name);
  7.   Assert.AreEqual("_sdfdsfsdfsfd", a.Foo().Name);
  8.  
  9.   var x = 0;
  10.  
  11.   dynamic proxy = DynamicProxy.Create(a).AddProperty<bool>("IsSelected")
  12.                                         .AddProperty("X", _ => x, (_, value) => x = value)
  13.                                         .AddProperty("LastName","FFFF")
  14.                                         .AddMethod("Boo", new Func<DynamicProxy<MyClass>, int, string>((m, i) => ((MyClass)m).Name + i.ToString()));
  15.                         
  16.  
  17.   proxy.Name = "567";
  18.   proxy.IsSelected = true;
  19.   proxy.X = 42;
  20.   var b = proxy.Foo();
  21.   b.IsSelected = true;
  22.  
  23.   Assert.AreEqual("567",a.Name);
  24.   Assert.AreEqual("5674",proxy.Boo(4));
  25.   Assert.IsTrue(proxy.IsSelected);
  26.   Assert.AreEqual(42, x);
  27.   Assert.IsTrue(b.IsSelected);
  28.  
  29.   b.IsSelected = false;
  30.  
  31.   Assert.IsTrue(proxy.IsSelected);
  32.   Assert.IsFalse(b.IsSelected);
  33.  
  34.   proxy.LastName = "890";
  35.   var d = proxy.Foo();
  36.   Assert.AreEqual("FFFF",d.LastName);
  37.  
  38.   var d2 = proxy.Foo();
  39.   d2.LastName = "RRRRR";
  40.  
  41.   Assert.AreEqual("567",Foo(proxy));
  42.  
  43.   Assert.AreEqual(d.LastName,d2.LastName);
  44.  
  45. //Можно грабить корованы делать импицитный каст
  46.  
  47.   var c = (MyClass)proxy;
  48.  
  49.   Assert.AreEqual("567",c.Name);
  50.  
  51.   foreach (var child in proxy.GetChilds())
  52.   {
  53.     child.IsSelected = true;
  54.     Assert.IsTrue(child.IsSelected);
  55.   }
  56. }
* This source code was highlighted with Source Code Highlighter.

Так же сделана автоматическая реализация INotifyPropertyChanged
  1. [TestMethod]
  2. public void TestPropertyChange()
  3. {
  4.   var myClass = new MyClass();
  5.   var propertyName = string.Empty;
  6.   dynamic proxy = DynamicProxy.Create(myClass);
  7.  
  8.   ((INotifyPropertyChanged) proxy).PropertyChanged += (s, a) => propertyName = a.PropertyName;
  9.  
  10.   proxy.Name = "aaaa";
  11.  
  12.   Assert.AreEqual("Name",propertyName);
  13. }
* This source code was highlighted with Source Code Highlighter.

Практическое применение


Давайте попробуем решить с помощью данного прокси задачу, которая описана в начале статьи. Для упрощения мы сгенерируем небольшое дерево регионов, дадим пользователю выбрать нужные и покажем справа список выбранных. Собственно вот так привыглядит код окна
  1. public MainWindow()
  2. {
  3.   InitializeComponent();
  4.   DataContext = this;
  5.   Items = new[] { DynamicProxy.Create(CreateRegions().First()).AddProperty<bool>("IsSelected") };
  6. }
  7.  
  8. IEnumerable<Region> GetSelectedItems(IEnumerable<dynamic> items)
  9. {
  10.   return items.Where(x => x.IsSelected).Concat(items.SelectMany(x => GetSelectedItems((IEnumerable<dynamic>)x.SubRegions))).Select(x=>(Region)x);
  11. }
  12.  
  13. private void ButtonClick(object sender, RoutedEventArgs e)
  14. {
  15.   var res = GetSelectedItems(Items).Take(10).ToList();
  16.   SelectedItems = res;
  17. }
* This source code was highlighted with Source Code Highlighter.

И xaml к нему
  1. <Window x:Class="WpfApplication1.MainWindow"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication1="clr-namespace:WpfApplication1"
  4.     Title="MainWindow" Height="350" Width="525">
  5.   
  6.   <Window.Resources>
  7.  
  8.     <DataTemplate DataType="{x:Type WpfApplication1:Region}">
  9.       <WrapPanel>
  10.         <TextBlock Text="{Binding Path=Name,StringFormat='{}{0}, '}"/>
  11.         <TextBlock Text="{Binding Path=Index}"/>
  12.       </WrapPanel>
  13.     </DataTemplate>
  14.     
  15.     
  16.   </Window.Resources>
  17.   <Grid>
  18.     <Grid.ColumnDefinitions>
  19.       <ColumnDefinition Width="*"/>
  20.       <ColumnDefinition Width="auto"/>
  21.       <ColumnDefinition Width="*"/>
  22.     </Grid.ColumnDefinitions>
  23.  
  24.     <TreeView Grid.Column="0" ItemsSource="{Binding Items}" BorderThickness="0">
  25.       <TreeView.ItemTemplate>
  26.         <HierarchicalDataTemplate ItemsSource="{Binding SubRegions}">
  27.           <CheckBox IsChecked="{Binding IsSelected,Mode=TwoWay}" Content="{Binding Value}" />          
  28.           
  29.         </HierarchicalDataTemplate>
  30.       </TreeView.ItemTemplate>
  31.     </TreeView>
  32.     <Button Content="Show selected" VerticalAlignment="Center" Grid.Column="1" Click="ButtonClick"/>
  33.     <ListBox Grid.Column="2" ItemsSource="{Binding SelectedItems}" BorderThickness="0" />
  34.   </Grid>
  35. </Window>
* This source code was highlighted with Source Code Highlighter.


Скриншот приложения


image

Ссылки


  1. Исходники проекта
  2. Скомпилированные бинарники
  3. DynamicObject
  4. MVVM
  5. Fluent interface

Спасибо за внимание.
Поделиться публикацией

Комментарии 21

    +1
    Присмотритесь так же к проекту Clay от создателей Orchard CMS. Там то же все элегантно сделано. Вот ссылки с документацией
    weblogs.asp.net/bleroy/archive/2010/08/16/clay-malleable-c-dynamic-objects-part-1-why-we-need-it.aspx
    weblogs.asp.net/bleroy/archive/2010/08/18/clay-malleable-c-dynamic-objects-part-2.aspx
      +1
      Ага, спасибо за ссылку. Но Clay позволяет делать вот так IPerson lou = people[0]; за счет использования DynamicProxy от Castle, а он может перехватывать только вызовы виртуальных методов либо методов интерфейса. Т.е. если у меня есть свойство Name и я хочу во вью-модели при сеттере добавить добавить какую-то валидацию на него(которая возможно мне совершенно не нужна или даже вредна в обычной дата-модели), то мне придется сделать свойство виртуальным. Это, конечно, не очень страшно, но мне было интересно сделать решение, которое позволяет не модифицировать сами модельные классы.
        +2
        ээээх, афтор, вам бы все пять способов сделать и замерить быстродействие! цены бы этой статье не было.

        но в целом спасибо!
          0
          Честно говоря, основная причина написания статьи — продемонстрировать возможности dynamic type. Но если интересно, то быстродействие я мерил — dynamic выигрывает на создании, ибо кастоловский прокси компилирует объект на лету с emit-апи, но зато потом в моей реализации идут проигрыши на вызове методов реального объекта, поскольку я дергаю их через рефлексию. Впрочем в дата-объекте особо методов быть не должно. Вызов свойств по времени вполне сравним, ибо там я прикрутил вызов через компилируемые лямбда-функции. Что касается решений типа T4 или посташарпа, то там конечно все быстрее, т.к. все нужные действия происходят на компиляции, но зато удобство написания явно ниже(особенно это касается Т4).
          0
          sdramare, я бы рекомендовал вам не использовать данный подход в продакшене. Сам часто грешу, используя именно динамики для этой задачи, хоть и в более простой форме чем вы.
          Но! Для большо́го количества моделей, с больши́ми уровнями вложенности — заметно тормозит. Лучше сделать свой extension для быстрой генерации ViewModel или взять чужой и переделать под себя. Для себя избрал именно такой способ. :)
            +1
            Спасибо за ссылку =) Насчет тормозов — я использовал этот подход для построение визуального редактора абстрактного синтаксического дерева некого упрощающего диалекта питона(в нем описывались формулы преобразования одного значения в другое). При общем количестве нод в графе порядка 50 никаких тормозов не видел. Впрочем ничего не берусь утверждать, все зависит от задачи и проекта.
              0
              Интересная задача. Даже дважды перечитал формулировку. :-)
              Я обычно для прототипов не сложных редакторов или для мелких утилит использую — там скорость работы действительно будет неотличима на глаз от обычного подхода. А вот для большого вложенного N-арного дерева (я пробовал подключить на работающий проект с «рукописными» VM), где каждая вершина с кучей вложенных моделей, тормоза были заметны невооруженным взглядом. Возможно, там хреновая архитектура для динамиков, но, для себя я выводы сделал.
            0
            добавь к сеттору файринг нотификации

            Капец :) Это ж вроде не перевод…
              0
              Опечатку поправил, спасибо. А за «сленг» — извиняюсь, привык за годы. «Добавить к методу установки значения свойства вызов события об изменении значения данного свойства» — так будет лучше?
                0
                Я думаю, можно ограничиться "… вызов соответствующего события". Как считаете?
              0
              Да, давайте так.
                0
                И знаете, в чем крупный и отвратительный недостаток этого подхода? В том, что ваш код перестал быть типизированным. Любые ошибки и опечатки вы отловите уже после компиляции.

                При этом вы зачем-то зациклились на прокси, хотя эти задачи прекрасно решаются с помощью обычных объектов и маппинга. А отсюда вырастает целый класс решений по этому поводу, начиная с AutoMapper.
                  0
                  И знаете, в чем крупный и отвратительный недостаток этого подхода? В том, что ваш код перестал быть типизированным. Любые ошибки и опечатки вы отловите уже после компиляции.

                  Ну во-первых да, знаю, что он перестанет быть типизированным(думаю многие об этом начали догадываться еще прочитав слово dynamic в заголовке). Во-вторых то, что это недостаток, еще очень спорный вопрос. Динамическая типизация имеет как свои плюсы, так и минусы. Посмотрите на тот же питон — на нем прекрасно пишутся приложения, хотя типизация самая что не наесть динамическая. Ну или обжект-си в плане посылки сообщений. Да, ошибки отлавливаются уже после компиляции и по-этому на динамических языках стараются писать через TDD. Впрочем я лично считаю, что и на статике это будет не лишним. Что же касается данного примера, то обратите внимает, что речь идет о довольно простых объект, которые не должны содержать сложную логику, которая сломается. К тому же тем не менее типизацию в каком виде я сохраняю — например если свойство было задано как int, записать в него строку уже не получится.

                  При этом вы зачем-то зациклились на прокси

                  Была поставлена задача и предложено решение на основе прокси. Сама задача была взята как база для рассмотрения вопроса о том, как можно использовать новый тип, появившийся в спецификации C# 4.0
                  эти задачи прекрасно решаются с помощью обычных объектов и маппинга. А отсюда вырастает целый класс решений по этому поводу, начиная с AutoMapper.

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

                  P.S. Ваш комментарий, на мой взгляд, чрезмерно эмоциональный и несет в себе конфликт-агенты. Мне кажется не стоит продолжать дискуссию в таком тоне, вы не находите?
                    0
                    «К тому же тем не менее типизацию в каком виде я сохраняю — например если свойство было задано как int, записать в него строку уже не получится.»
                    С точки зрения компилятора — получится.

                    «Сама задача была взята как база для рассмотрения вопроса о том, как можно использовать новый тип, появившийся в спецификации C# 4.0»
                    А это как раз типическая ошибка подхода: «у нас есть новая возможность, как ее использовать». В то время как правильнее исходить из «у нас есть такая задача, как ее решить».

                    «Прекрасно если это так, с удовольствием прочитаю вашу статью об этом.»
                    habrahabr.ru/search/?q=automapper

                    «И в вдогонку — когда вы начинаете использовать дататемплейты, стили, триггеры и байдинги в WPF, то очень часто проверка ошибок на этапе компиляции превращается в пыль»
                    Мир не ограничивается WPF, вот в чем дело. MVVM используется не только там, и задача создания моделей возникает не только там. В вашей статье WPF возникает только в конце, а до этого вы говорите об абстрактном MVVM.
                      0
                      С точки зрения компилятора — получится.

                      Безусловно. Но в данном случае гарантируется целостность данных — если произойдет некорректный сет вы об этом сразу узнаете.

                      А это как раз типическая ошибка подхода: «у нас есть новая возможность, как ее использовать». В то время как правильнее исходить из «у нас есть такая задача, как ее решить».

                      Как известно одна и даже задача, сформулированная в общих терминах имеет множество решений. Выбор нужного делается на основе тех или иных критериев оптимальности. Я уже говорил ранее, динамическая типизация имеет свои плюсы и соответственно предпочтительна для решения определенных классов задач. Вы же почему-то отбрасываете ее в принципе, исходя из тезиса «не компилируется — значит плохо». Вас не смущается, что множество веб-решений, комплексная сложность которых вполне сравнима(а то и превосходит) со сложностью десктоп приложений, делаются на языках с динамической типизацией(причем даже менее строгой, чем та, которой привел я в своей статье)?
                      habrahabr.ru/search/?q=automapper

                      Вы сами ходили по этой ссылке? Если да, то в какой из этих статей решается задача «автоматического добавления новых свойств для объектов, возвращаемых методами и функциям»? Мне лично кажется, что не в какой, ибо автомаппер в принципе не предназначен для этого. Кстати говоря, что будет, если в одном из классов, который мы маппим, я поменяю имя свойства? А ничего не будет, все прекрасно скомпилируется. Ошибка будет уже на рантайме.

                      Мир не ограничивается WPF, вот в чем дело. MVVM используется не только там, и задача создания моделей возникает не только там. В вашей статье WPF возникает только в конце, а до этого вы говорите об абстрактном MVVM.
                      Я вам просто пример привел. Не нравится WPF — возьмите Silverlight. А в связке (html+css+js), на которой строятся современные веб-интерфейсы, статической типизацией и не пахло. И ничего, никто не умирает.

                      Я понимаю, если бы вы мне указали на то, что на текущий момент dynamic плохо поддерживается статическими анализаторами кода. Это да, недостаток. А статика сейчас все больше сдает позиции.
                        0
                        «Безусловно. Но в данном случае гарантируется целостность данных — если произойдет некорректный сет вы об этом сразу узнаете.»
                        Не сразу, а на этапе выполнения.

                        «вы же почему-то отбрасываете ее в принципе, исходя из тезиса «не компилируется — значит плохо».»
                        Я ее не отбрасываю. Я просто при прочих равных предпочитаю решения, которые компилируются (пусть даже они требуют, скажем, кодогенератора).

                        «Кстати говоря, что будет, если в одном из классов, который мы маппим, я поменяю имя свойства?»
                        … зато если вы поменяете это свойство в модели или ошибетесь в обращении к нему, все сломается на этапе компиляции.

                        «Не нравится WPF — возьмите Silverlight.»
                        … и замлом мир тоже не ограничивается, в общем-то.

                        «на текущий момент dynamic плохо поддерживается статическими анализаторами кода»
                        Проблема в том, что его вообще очень сложно поддержать анализаторами кода, потому что public void Process(dynamic smth) может быть вызван откуда угодно, и в динамике может быть что угодно. Прощай, анализ. Именно поэтому я предпочту кодогенерацию или типизованный прокси (хоть на рефлекшне) динамику.

                        «А в связке (html+css+js), на которой строятся современные веб-интерфейсы, статической типизацией и не пахло.»
                        И знаете, как это усложняет разработку? По сравнению с нормальным типизованным asp.net mvc?
                    0
                    И в вдогонку — когда вы начинаете использовать дататемплейты, стили, триггеры и байдинги в WPF, то очень часто проверка ошибок на этапе компиляции превращается в пыль. Вы никогда не сталкивались с ситуацией, когда байдинг на какой-нибудь прекрасно скомпилированной странице/контроле не работает, т.к. не правильно заданно имя свойства или в датаконтексте по воли злого рока оказался объект не того типа, которого вы ожидали? Является наличие таких особенностей при построении UI через хaml и WPF поводом отказаться от их использования?
                      0
                      Статическая типизация это вообще спорная вещь, сама по себе много не дает, зато ее отсутствие подталкивает к написанию модульных тестов, TDD и т.п. Тем более, вы эту типизацию затем все равно теряете в xaml.

                      Насколько я понимаю, автор как раз хотел избежать рутинной работы по созданию таких объектов, большинство которых скорее всего ничем 'интеллектуальным' заниматься не будут, кроме как оборачивать и пробрасывать вызовы, что по сути и есть проксирование.
                      Ваш подход мне совсем непонятен. Зачем создавать кучу view models, которые еще и никак не будут связаны с моделью, кроме как логически, да еще и маппинги прописывать? И да, я бы тогда уж с EmitMapper начинал.

                      На мой взгляд вариантом тут может являться применение шаблона для генерации делегирующего свойства с оповещением нотификации, если говорить о INotifyPropertyChanged. Если говорить о добавочных свойствах, то я думаю, что свойства в студии генерируются проще, чем при подходе автора. А вот с оборачиванием внутренних объектов во view models придется возиться.
                        0
                        «Статическая типизация это вообще спорная вещь»
                        Для кого как.

                        «ее отсутствие подталкивает к написанию модульных тестов, TDD и т.п.»
                        А ее наличие — к выявлению части ошибок еще на этапе компиляции, экономя мне минуты при сборке.

                        «Тем более, вы эту типизацию затем все равно теряете в xaml.»
                        Замлом жизнь MVVM не ограничивается.

                        «Зачем создавать кучу view models, которые еще и никак не будут связаны с моделью, кроме как логически, да еще и маппинги прописывать?»
                        Чтобы получить чистую view model, не дающую побочных эффектов на бизнес-объект, ее породивший.
                          0
                          «Для кого как.»
                          На мой взгляд существенным преимуществом статической является упрощение статического анализа, иначе с этим возникают серьезные проблемы. А так по опыту могу сказать, что у многих статическая создает ложное чувство корректности программы, в то время как контролирует только часть ошибок, и то глупых. Впрочем на эту тему уже столько сказано и написано, что статическая типизация это все же безусловно спорная вещь.

                          «А ее наличие — к выявлению части ошибок еще на этапе компиляции, экономя мне минуты при сборке.»
                          Либо вы работаете над гигантскими проектами и я вам завидую, либо существует проблема запутанности зависимостей, возможно архитектуры. Опять же по опыту могу сказать, что программисты как правило нажимают в студии кнопочку Build и компилятор начинает молотить до победного конца. Если подходить по уму, то в случае с модульными тестами у вас возникнает 'пролема' создания более продуманной структуры зависимостей, что в конечном счете позволяет минимизировать выигрышь обрывания процесса компиляции при обнаружении первой ошибки.

                          «Тем более, вы эту типизацию затем все равно теряете в xaml.»
                          Ок, может быть вы его прикрутили куда-то еще. Но MVVM в первую очередь относится к Microsoft, WPF, Silverlight, XAML.

                          «Чтобы получить чистую view model, не дающую побочных эффектов на бизнес-объект, ее породивший.»
                          Вы имели в виду объект, который оборачивает View model, а не порождает?
                          По паттерну view model никак не может аффектить бизнес-объекты. Зато по тому же паттерну View model занимается связыванием model и view, по сути он содержит в себе бизнес-объекты и обеспечивает работу с ними. Подмена модели другими объектами, не связанными с моделью выглядит странно.
                            0
                            «Либо вы работаете над гигантскими проектами»
                            Достаточно большим, чтобы компиляция шла долго. Обрыв ее на ранней стадии полезен.

                            «Но MVVM в первую очередь относится к Microsoft, WPF, Silverlight, XAML.»
                            Он немножко старше.

                            «Вы имели в виду объект, который оборачивает View model, а не порождает?»
                            Я имею в виду именно порождает. Я просто не считаю, что изменения в viewmodel должны всегда сразу идти в связанную domain model.

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

                    Самое читаемое