<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Just Developer Blog, заметки с тегом: Generics</title>
<link>http://bibobo.ru/tags/generics/</link>
<description></description>
<generator>E2 (v3254; Aegea)</generator>

<item>
<title>Пример использования Обобщений (Generics) в C#</title>
<guid isPermaLink="true">http://bibobo.ru/all/primer-ispolzovaniya-oboscheniy-generics-v-c/</guid>
<link>http://bibobo.ru/all/primer-ispolzovaniya-oboscheniy-generics-v-c/</link>
<comments>http://bibobo.ru/all/primer-ispolzovaniya-oboscheniy-generics-v-c/</comments>
<description>&lt;p&gt;Давеча потребовалось реализовать инфраструктуру объектов-оберток для редактора роллет.&lt;br /&gt;
&lt;br&gt;&lt;br /&gt;
Исходная задача была следующей:&lt;br /&gt;
— Есть построитель роллет, который имеет ряд коллекций с объектами разных типов, описывающими один из элементов роллеты (привод, направляющая, ламель и т. п.)&lt;br /&gt;
— Необходимо реализовать логику расчета роллеты по уже заполненному списку параметров&lt;br /&gt;
— Внутренние типы построителя наследуются от базового класса &lt;i&gt;RollCalc.Model.CommonProperties&lt;/i&gt;&lt;br /&gt;
&lt;br&gt;&lt;br /&gt;
Для упрощения реализации было решено создать свой набор объектов, описывающих каждый из элементов роллеты, чтобы не иметь жесткую связь с внутренними объектами редактора (ибо работать с ними было весьма не удобно). В своих объектах можно реализовать любой набор методов и свойств, которые существенно облегчат расчет и избавят нас от хардкода.&lt;br /&gt;
Естественно создавать свои объекты и заполнять их базовые свойства нужно по внутренним коллекциям редактора и желательно парой строчек кода. Пожалуй в данной ситуации наилучший выход — использовать обобщения языка C#. Они позволят нам написать бизнес логику один раз и использовать ее для разных классов (логика-то сходная).&lt;br /&gt;
&lt;input id="button_primer_ispolzovaniya_oboscheniy_generics_v_c" type="button" onclick="toggle(cut_primer_ispolzovaniya_oboscheniy_generics_v_c, button_primer_ispolzovaniya_oboscheniy_generics_v_c)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_primer_ispolzovaniya_oboscheniy_generics_v_c" style="display: none;"&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Построим инфраструктуру следующим образом:&lt;/p&gt;
&lt;p&gt;— Реализуем абстрактный базовый класс &lt;i&gt;RollCalcElementBase&lt;T&gt;&lt;/i&gt;, который будет содержать всю логику по заполнению общих полей для всех типов-наследников. Почему именно базовый класс, а не интерфейс? Чтобы не париться с реализацией базовой логики в каждом классе-наследнике.&lt;br /&gt;
— В базовом классе обязательно реализуем метод &lt;i&gt;public virtual void InitFromPresentationObject(T presentationObject)&lt;/i&gt;, который будет инициализировать экземпляр базового класса по его &lt;i&gt;представлению&lt;/i&gt; из объекта редактора роллет&lt;br /&gt;
— Реализуем произвольное количество классов-наследников от &lt;i&gt;RollCalcElementBase&lt;T&gt;&lt;/i&gt;, в которых можно будет расширить функционал базового класса или переопределить его&lt;br /&gt;
— В основном классе программы создадим коллекции наших объектов-наследников&lt;br /&gt;
— Там же, в основном классе добавим метод, создающий наши объекты по коллекциям объектов редактора&lt;br /&gt;
— Плюс нужен будет метод, получающий экземпляр одного из наших объектов по его типу и наименованию &lt;i&gt;(так уж сложилось, что расчет спецификации роллеты сильно зависит от производителя, а значит и от артикула того или иного элемента)&lt;/i&gt;&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Весь код был упрощен, что бы не раздувать статью.&lt;/h3&gt;
&lt;p&gt;Обязательно перед чтением кода необходимо понять как можно накладывать ограничения на использование типов в обобщенном классе или методе. Очень хорошо про это описано на MSDN в статье &lt;a href="http://msdn.microsoft.com/ru-ru/library/d5x73970.aspx"&gt;Ограничения параметров типа&lt;/a&gt;&lt;br /&gt;
&lt;br&gt;&lt;br /&gt;
Код базового класса и классов-наследников:&lt;br /&gt;
&lt;input type="button" onclick="toggle(code1_primer_ispolzovaniya_oboscheniy_generics_v_c)" value="Нажмите, чтобы отобразить или спрятать код"&gt;&lt;/p&gt;
&lt;pre id="code1_primer_ispolzovaniya_oboscheniy_generics_v_c" style="display: none;"&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
    /// Базовый класс элемент роллет
    /// Обратите внимание на предложение where
    /// where T : RollCalc.Model.CommonProperties - накладываем ограничение на использование типа в обобщенном классе, только тип элемента построителя роллет
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;typeparam name=&amp;quot;T&amp;quot;&amp;gt;Элемент построителя роллет&amp;lt;/typeparam&amp;gt;
    public abstract class RollCalcElementBase&amp;lt;T&amp;gt; where T : RollCalc.Model.CommonProperties
    {
        public string Name { get; set; }

        private bool _enabled;

        public bool Enabled
        {
            get { return _enabled; }
            set
            {
                _enabled = value;
                if (PresentationObject != null) PresentationObject.Visible = value;
            }
        }
        public T PresentationObject { get; set; }

        /// &amp;lt;summary&amp;gt;
        /// Инициализировать из объекта роллеты
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&amp;quot;presentationObject&amp;quot;&amp;gt;объект роллеты&amp;lt;/param&amp;gt;
        public virtual void InitFromPresentationObject(T presentationObject)
        {
            Name = presentationObject.Description;
            Enabled = presentationObject.Visible;
            PresentationObject = presentationObject;

            presentationObject.Tag = this;
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// Профиль ламели
    /// &amp;lt;/summary&amp;gt;
    public class Profile : RollCalcElementBase&amp;lt;LamellaProfile&amp;gt;
    {
    }

    /// &amp;lt;summary&amp;gt;
    /// Тип короба
    /// &amp;lt;/summary&amp;gt;
    public class Collar : RollCalcElementBase&amp;lt;ProtectiveCollarType&amp;gt;
    {
    }

    /// &amp;lt;summary&amp;gt;
    /// Привод роллеты
    /// &amp;lt;/summary&amp;gt;
    public class Drive : RollCalcElementBase&amp;lt;DriveSystem&amp;gt;
    {
        public int MaxWeight { get; set; }

        public bool CalcEnabled(decimal rollWidth)
        {
            Enabled = rollWidth &amp;lt;= MaxWeight;
            return Enabled;
        }

        public override void InitFromPresentationObject(DriveSystem presentationObject)
        {
            object tag = presentationObject.Tag;

            base.InitFromPresentationObject(presentationObject);

            if (tag is int)
            {
                MaxWeight = Convert.ToInt32(tag);
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;br /&gt;
Код из основного класса (коллекции наших объектов, а так же методы загрузки и получения экземпляра нашего объекта:&lt;br /&gt;
&lt;input type="button" onclick="toggle(code2_primer_ispolzovaniya_oboscheniy_generics_v_c)" value="Нажмите, чтобы отобразить или спрятать код"&gt;&lt;/p&gt;
&lt;pre id="code2_primer_ispolzovaniya_oboscheniy_generics_v_c" style="display: none;"&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;private List&amp;lt;Drive&amp;gt; _drives = new List&amp;lt;Drive&amp;gt;();
        public List&amp;lt;Drive&amp;gt; Drives {get { return _drives; }}

        private List&amp;lt;Profile&amp;gt; _profiles = new List&amp;lt;Profile&amp;gt;();
        public List&amp;lt;Profile&amp;gt; Profiles{get { return _profiles; }}

        private List&amp;lt;Collar&amp;gt; _collars = new List&amp;lt;Collar&amp;gt;();
        public List&amp;lt;Collar&amp;gt; Collars{get { return _collars; }}

        /// &amp;lt;summary&amp;gt;
        /// Загрузка и связывание элемента из списка элементов построителя роллет
        /// Обратите внимание на предложения where в заголовке метода
        /// where T : RollCalc.Model.CommonProperties - ограничивает нас по исходному типу объекта роллеты
        /// where T2 : RollCalcElementBase&amp;lt;T&amp;gt;, new() - вводит следующее ограничение: наследник базового класса RollCalcElementBase&amp;lt;T&amp;gt;, имеющий конструктор без параметров (т.е. именно НАСЛЕДНИК, подставить сюда базовый класс уже не получится, поскольку он абстрактный и сконструировать его мы не сможем
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;typeparam name=&amp;quot;T&amp;quot;&amp;gt;элемент построителя&amp;lt;/typeparam&amp;gt;
        /// &amp;lt;typeparam name=&amp;quot;T2&amp;quot;&amp;gt;наш связанный элемент&amp;lt;/typeparam&amp;gt;
        /// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;        
        public void LoadFromElementList&amp;lt;T, T2&amp;gt;(IList&amp;lt;T&amp;gt; list)
            where T : RollCalc.Model.CommonProperties
            where T2 : RollCalcElementBase&amp;lt;T&amp;gt;, new()
        {
            List&amp;lt;T2&amp;gt; elements = new List&amp;lt;T2&amp;gt;();

            //создаем наши объекты по объектам построителя, складываем их в список
            foreach (T sourceElement in list)
            {
                T2 element = new T2();
                element.InitFromPresentationObject(sourceElement);
                elements.Add(element);
            }

            //та самая магия, выбираем по типу нашего объекта соответствующую переменную коллекции и присваиваем нашу коллекцию ей
            if (typeof (T2) == typeof (Drive)) _drives = elements.Cast&amp;lt;Drive&amp;gt;().ToList();
            if (typeof(T2) == typeof(Profile)) _profiles = elements.Cast&amp;lt;Profile&amp;gt;().ToList();
            if (typeof (T2) == typeof (Collar)) _collars = elements.Cast&amp;lt;Collar&amp;gt;().ToList();
        }

        /// &amp;lt;summary&amp;gt;
        /// Возвращает элемент по имени
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;typeparam name=&amp;quot;T&amp;quot;&amp;gt;&amp;lt;/typeparam&amp;gt;
        /// &amp;lt;param name=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public T GetElementByName&amp;lt;T&amp;gt;(string name)
            where T : class
        {
            //объявляем переменную коллекции нашего базового типа
            List&amp;lt;RollCalcElementBase&amp;lt;RollCalc.Model.CommonProperties&amp;gt;&amp;gt; list = null;

            //делаем обратное действие - по переданному типу Т ищем правильную коллекцию наших объектов-наследников и кастуем их к базовому типу
            if (typeof (T) == typeof (Drive))
            {
                list = _drives.Cast&amp;lt;RollCalcElementBase&amp;lt;RollCalc.Model.CommonProperties&amp;gt;&amp;gt;().ToList();
            }
            if (typeof (T) == typeof (Profile))
            {
                list = _profiles.Cast&amp;lt;RollCalcElementBase&amp;lt;RollCalc.Model.CommonProperties&amp;gt;&amp;gt;().ToList();
            }
            if (typeof (T) == typeof (Collar))
            {
                list = _collars.Cast&amp;lt;RollCalcElementBase&amp;lt;RollCalc.Model.CommonProperties&amp;gt;&amp;gt;().ToList();
            }

            //ищем экземпляр в коллекции по наименованию и возвращаем его (при этом кастуем его к типу-наследнику)
            if (list != null)
            {
                foreach (RollCalcElementBase&amp;lt;CommonProperties&amp;gt; elementBase in list)
                {
                    if (elementBase.Name.ToLower() == name.ToLower())
                    {
                        return elementBase as T;
                    }
                }
            }

            return null;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;br /&gt;
Вот пожалуй и все пироги, на последок пример кода формирования коллекции наших объектов по базовой коллекции построителя:&lt;/p&gt;
&lt;pre&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;RollCalcSettings.Settings.LoadFromElementList&amp;lt;DriveSystem, Drive&amp;gt;(settings.DriveSystems);
RollCalcSettings.Settings.LoadFromElementList&amp;lt;LamellaProfile, Profile&amp;gt;(settings.LamellaProfiles);
RollCalcSettings.Settings.LoadFromElementList&amp;lt;ProtectiveCollarType, Collar&amp;gt;(settings.ProtectiveCollarTypes);&lt;/code&gt;&lt;/pre&gt;
&lt;/pre&gt;
&lt;p&gt;Используя такой подход мы добились следующего:&lt;br /&gt;
— Избавили себя от мороки в работе с исходными объектами построителя&lt;br /&gt;
— Сохранили связь с исходными объектами (свойство &lt;i&gt;public T PresentationObject { get; set; }&lt;/i&gt; в базовом классе)&lt;br /&gt;
— Добавили гибкости всей системе за счет возможности расширять функционал наследников наших объектов, например тот или иной элемент роллеты может или не может иметь цвет&lt;br /&gt;
— Реализовали один метод инициализации и один метод получения экземпляра для любого типа объекта в системе&lt;br /&gt;
— Узнали тонкости в накладывании ограничений на использование обобщенного типа в классе или методе&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Sat, 03 Jan 2015 13:43:56 +0300</pubDate>
</item>


</channel>
</rss>