Обновить
58

Пользователь

51
Подписчики
Отправить сообщение
Использование DTO — это лишь один из частных случаев в разработке.
Хотелось бы акцентировать внимание на том, что помимо сериализации библиотека предназначена для более широкого круга задач.

К примеру, имеется два DTO объекта, и их нужно сравнить. Можно, конечно, написать свою логику сравнения, но проще и быстрее сделать сопоставление снимков, как показано в статье.
Такой вышколенный текст — Вам бы редактором журнала работать =)

Ясно чувствуется, что статью долго и очень усердно воплощали, временами, я бы сказал, дотошно, поэтому и добавить-то особенно нечего.
Но будьте осторожны с этим впредь, ведь как вы и упомянули, программистов пугает такая дотошность руководства, когда не остаётся места для творческого самовыражения…

А как раз-таки именно творческий подход, на мой взгляд, порой подсказывает в программировании самые неожиданные архитектурные решения, до которых заранее, ну, никак не дойдёшь, только в самом процессе и итеративно.

Спасибо за труд и литературность материала!
Не спорю, здесь есть к чему придраться за нарушение «правильной» идеологии :)

Но, на мой взгляд, лучше использовать универсальный способ для решения обширного круга проблемных ситуаций, чем каждый раз изобретать новый хак.
Каким бы ни было ужасным явление Code Behind, стоит признать, что в некоторых ситуациях его применение уместно. Конечно, тут важно не переусердствовать, поэтому ответственность лежит на разработчике.

Представленный в статье «трюк» позволяет обойти некоторые ограничения при выполнении привязок. Безусловно, когда мы получаем больше свободы стоит помнить о безопасности, однако и возможности наши возратают.
Согласен, что такие ситуации скорее исключение из правил, но в то же время они очень «неудобные».

В примере свойство Text привязывается к свойству Number вью-модели через конвертер, но по условию к этому числу нужно добавить заголовок окна приложения.

Если мы будем делать дополнительное свойство Title во вью-модели, то она будет загрязняться интерфейсным кодом, а также придётся использовать что-то вроде MultiBinding, который, кстати, не на всех платформах поддерживается, плюс создавать IMultiValueConverter. Всё довольно усложняется несмотря на тривиальность задачи. С Inline Converter выглядит просто и понятно.

Также может возникнуть и другая ситуация: основная привязка свойства Text должна идти к свойству Number во вью-модели, но, например, в зависимости от нескольких других различных значений свойств этой вью модели, сама строка должна декорироваться тем или иным образом. Поскольку в обычном конвертере у нас нет доступа к самой вью-модели, приходится искать обходные пути, не всегда красивые. В нашем же случае есть прямой доступ к контесту данных представления, нужной нам вью-модели и всем её свойствам.
Думал над вашим вопросом, и пришёл к выводу, что, возможно, не совсем вас понял.
Вы имеете в виду удаление элементов из строки во время работы XAML-дизайнера среды разработки?

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

Конечно, у вас своё видение, но, по-моему, он стал компактнее в разы да и сложность его не такая высокая :)
Правильнее, на мой взгляд, установить у ненужного элемента свойство Visibility в состояние Collased, либо же использовать ItemsControl. Даже если вы удалите строку номер два, то сам контрол останется в визуальном дереве и код останется засорён, если это можно так назвать.

Хотелось бы иметь возможность указывать номер строки/столбца не абсолютно, а как-нибудь относительно.

В статье описан сам принцип и базовый синтаксис, усовершенствовать реализацию можно как угодно, на что хватит фантазии :)
Скажите, вы пробовали заново переопубликовывать ваши игры, но под другими названиями? Только предварительно подготовившись…

Например, можно использовать старую базу пользователей и попросить их обновиться до свежей версии вручную, как раз в тот момент, когда вы опубликуете апдейт, но уже как новое приложение. Опыт на Windows Phone показывает, что если не воспользоваться сполна первыми неделями сразу после публикации, то никакими вменяемыми действиями и крутыми апдейтами уже ситуацию с приложением не выправишь. Шансы попасть в какой-либо топ максимальны лишь сразу после публикации, а потом крайне резко снижаются.
Материал обновлён.
Замечу насчёт композиции функций, что SkipByRing(x).TakeByRing(y) в кольцевом обобщении не равнозначно SliceByRing(x,y), хотя в простых случаях, когда нет полного обхода кольца, они дают идентичный результат.
Как вам такой способ?
        private static void Main(string[] args)
        {
            var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'};
            var headIndexes = new[] { 0 ,  1 ,  2 ,  3 ,  4 ,  5 ,  6 ,  7 };
            var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 };

            // CDEFGICDEF
            bodyLetters.SkipByRing(18).TakeByRing(10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // FEDCBAFEDC
            bodyLetters.SkipByRing(-18).TakeByRing(10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // IGFEDCIGFE
            bodyLetters.SkipByRing(18).TakeByRing(-10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // ABCDEFABCD
            bodyLetters.SkipByRing(-18).TakeByRing(-10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.WriteLine();

            // CDEFGIABCD
            bodyLetters.SliceByRing(18, 10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // GIABCDEFGI
            bodyLetters.SliceByRing(-18, 10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // BAIGFEDCBA
            bodyLetters.SliceByRing(18, -10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // FEDCBAIGFE
            bodyLetters.SliceByRing(-18, -10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.ReadKey();
        }

Имплементация
        // ReSharper disable PossibleMultipleEnumeration
        // ReSharper disable LoopCanBePartlyConvertedToQuery
        public static IEnumerable<T> SkipByRing<T>(this IEnumerable<T> source, int count)
        {
            var originalCount = 0;
            var reverse = count < 0;
            count = reverse ? -count : count;
            source = reverse ? source.Reverse() : source;

            while (true)
            {
                if (originalCount > 0) count %= originalCount;
                foreach (var item in source)
                {
                    originalCount++;
                    if (count > 0)
                    {
                        count--;
                        continue;
                    }
                    yield return item;
                }

                if (count == 0) yield break;
            }
        }

        public static IEnumerable<T> TakeByRing<T>(this IEnumerable<T> source, int count)
        {
            var reverse = count < 0;
            count = reverse ? -count : count;
            source = reverse ? source.Reverse() : source;

            while (true)
            {
                foreach (var item in source)
                {
                    if (count > 0)
                    {
                        count--;
                        yield return item;
                    }
                }

                if (count == 0) yield break;
            }
        }

        public static IEnumerable<T> SliceByRing<T>(this IEnumerable<T> source, int skipCount, int takeCount)
        {
            var originalCount = 0;
            var skipReverse = skipCount < 0;           
            var takeReverse = takeCount < 0;
            skipCount = skipReverse ? -skipCount : skipCount;
            takeCount = takeReverse ? -takeCount : takeCount;
            source = takeReverse ? source.Reverse() : source;

            if (skipReverse ^ takeReverse)
            {
                var count = source.Count();
                skipCount = count - skipCount % count;
            }

            while (true)
            {
                if (originalCount > 0) skipCount %= originalCount;
                foreach (var item in source)
                {
                    originalCount++;
                    if (skipCount > 0)
                    {
                        skipCount--;
                        continue;
                    }

                    if (takeCount > 0)
                    {
                        takeCount--;
                        yield return item;
                    }
                }

                if (takeCount == 0) yield break;
            }
        }

Так может надо подумать немножко дольше, чтобы недочетов не было?

Тогда бы я отвечал на ваши комментарии раз в день и прогресс шёл значительно медленнее.

Кстати, придумал практическое приложение методам Ring и Turn. К примеру, вы делаете UI, и у вас есть коллекция-заглушка с n-элементами, вдруг вы захотели проверить, как работает UI при 2n, 3n, ...mn элементах. Вам достаточно написать что-то вроде Items = _testItems.Ring(0, m).ToList() и всё.

Удобно же, не находите? )
Решения я генерирую в реальном времени, поэтому появление недочётов вполне закономерно.

Вы можете предложить свои оптимизации, мне самому интересно. Менять тип возвращаемого значения я не вижу смысла, так как сам метод Ring внутри срабатывает за O(1), а на выходе при необходимости можно сделать как ToList(), так и ToArray().

Также придумал последнее обобщение с количеством оборотов =)

        public static IEnumerable<T> Turn<T>(this IList<T> items, int skip, int turnsCount = 0)
        {
            var reverse = skip < 0;
            var count = items.Count;
            skip = reverse ? count + skip : skip;
            var take = turnsCount == 0
                ? reverse ? -skip - 1 : count - skip
                : count*turnsCount;
            return items.Ring(skip, take);
        }

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

Понимаю, что в обобщённой версии есть несколько дополнительных булевых проверок, но всё же их влияние не столь велико на производительность.

Разве что

var index = reverse ? skip - j : skip + j;

в цикле, возможно, имеет смысл разделить на два цикла, хотя это лучше проверить на практике.
ToList я делаю лишь для того, чтобы сэкономить строчки кода в тестовом примере и только (встроенный метод ForEach() есть только у List), поэтому он здесь не обязателен.

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

Ring
        public static IEnumerable<T> Ring<T>(this IList<T> items, int skip)
        {
            var reverse = skip < 0;
            var count = items.Count;
            skip = reverse ? count + skip : skip;
            var take = reverse ? -skip - 1 : count - skip;
            return items.Ring(skip, take);
        }

        public static IEnumerable<T> Ring<T>(this IList<T> items, int skip, int take)
        {
            var reverse = take < 0;
            var count = items.Count;
            skip = skip < 0 ? count + skip : skip;
            skip = skip < count ? skip : skip%count;
            take = reverse ? -take : take;

            for (var i = 0; i < take; i++)
            {
                var j = i < count ? i : i%count;
                var index = reverse ? skip - j : skip + j;
                index = index < 0 ? count + index : index;
                index = index < count ? index : index%count;
                yield return items[index];
            }
        }

        private static void Main(string[] args)
        {
            var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'};
            var headIndexes = new[] { 0 ,  1 ,  2 ,  3 ,  4 ,  5 ,  6 ,  7 };
            var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 };

            // 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B',
            bodyLetters.Ring(2, 8).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D',
            bodyLetters.Ring(2, -8).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G'
            bodyLetters.Ring(3, 4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G'
            bodyLetters.Ring(-5, 4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'C', 'B', 'A'
            bodyLetters.Ring(-5, -4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C'
            bodyLetters.Ring(3, 16).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E'
            bodyLetters.Ring(-5, -16).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G', 'I'
            bodyLetters.Ring(3).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'A', 'B', 'C', 'D'
            bodyLetters.Ring(-5).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.ReadKey();
        }

Хорошо, соглашусь, что с флагами нужно быть осторожным.

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

var letters = new [] {'A', 'B, 'C', 'D'};
letters.Slice(1, 9); // BCDABCDAB
letters.Slice(-2, -9); // CBADCBADC
А это по сути не одно и то же?

При создании регулярного выражения мы можем указать флаг RegexOptions.IgnoreCase, вместе с тем можем не указывать, а использовать inline character i — результат тот же.

Конечно, если вы видите другие пути к консистентности, то можете их предложить, я со своей колокольни рассуждаю :)

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность