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

<channel>

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

<item>
<title>Использование QLPreviewController для просмотра файлов (iOS)</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</guid>
<link>http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</link>
<comments>http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</comments>
<description>&lt;p&gt;Для реализации просмотра файлов различных форматов (изображения, word, excel, txt, медиа и т. п.) в iOS существует очень удобный класс QLPreviewController. Для его удобного использования необходимо сделать следующее:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать класс-наследник QLPreviewController, который инкапсулирует все детали реализации&lt;/li&gt;
&lt;li&gt;Создать класс QLPreviewControllerDataSource, который будет являться источником данных для контроллера&lt;/li&gt;
&lt;li&gt;Создать классы-наследники QLPreviewItem, которые будут хранить информацию о просматриваемом элементе&lt;br /&gt;
&lt;input id="button_ios_qlpreview" type="button" onclick="toggle(cut_ios_qlpreview, button_ios_qlpreview)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div id="cut_ios_qlpreview" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Без лишних слов сразу взглянем на код контроллера&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Контроллер, который позволяет отобразить документы (изображения, ворд, экзель, пдф и т.п.) в виде превью
/// поддерживает несколько документов для отображения
/// &amp;lt;/summary&amp;gt;
public class QuickLookPreviewController : QLPreviewController
{
    /// &amp;lt;summary&amp;gt;
    /// Отобразить список NSUrl'ов
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;, if urls was previewed, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt; otherwise.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;urls&amp;quot;&amp;gt;Urls.&amp;lt;/param&amp;gt;
    public bool PreviewUrls(params NSUrl[] urls)
    {
        List&amp;lt;QLPreviewItem&amp;gt; items = new List&amp;lt;QLPreviewItem&amp;gt;();
        foreach (NSUrl url in urls)
        {
            QLPreviewItemNsUrl item = new QLPreviewItemNsUrl(url);
            if (QLPreviewController.CanPreviewItem(item))
            {
                items.Add(item);
            }
        }

        return PreviewItems(items.ToArray());
    }

    /// &amp;lt;summary&amp;gt;
    /// Отобразить список QLPreviewItem
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;, if items was previewed, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt; otherwise.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;items&amp;quot;&amp;gt;Items.&amp;lt;/param&amp;gt;
    public bool PreviewItems(params QLPreviewItem[] items)
    {
        if (items.Length &amp;gt; 0)
        {
            DataSource = new PreviewControllerDataSource(items);
            return true;
        }

        return false;
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview источник данных
    /// &amp;lt;/summary&amp;gt;
    private class PreviewControllerDataSource : QLPreviewControllerDataSource
    {
        private QLPreviewItem[] _items = new QLPreviewItem[0];

        public PreviewControllerDataSource(params QLPreviewItem[] items)
        {
            _items = items;
        }

        public override nint PreviewItemCount(QLPreviewController controller)
        {
            return _items.Length;
        }

        public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
        {
            return _items[index];
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий NSUrl
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemNsUrl : QLPreviewItem
    {
        private NSUrl _documentUrl;

        public QLPreviewItemNsUrl(NSUrl documentUrl)
        {
            _documentUrl = documentUrl;
        }

        public override string ItemTitle
        {
            get
            {
                return _documentUrl.LastPathComponent;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                return _documentUrl;
            }
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий имя и путь к файлу из файловой системы
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemFileSystem : QLPreviewItem
    {
        string _fileName, _filePath;

        public QLPreviewItemFileSystem(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                return NSUrl.FromFilename(_filePath);
            }
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий имя ипуть к файлу из бандла
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemBundle : QLPreviewItem
    {
        string _fileName, _filePath;
        public QLPreviewItemBundle(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                var documents = NSBundle.MainBundle.BundlePath;
                var lib = Path.Combine(documents, _filePath);
                var url = NSUrl.FromFilename(lib);
                return url;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Использовать данный код можно следующим образом:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;QuickLookPreviewController previewController = new QuickLookPreviewController();
if (previewController.PreviewUrls(new NSUrl[]{ fileUrl }))
{
    this.NavigationController.PushViewController(previewController, true);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Wed, 13 Jan 2016 22:10:14 +0300</pubDate>
</item>

<item>
<title>Использование Fragment без UI для хранения данных</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</guid>
<link>http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</link>
<comments>http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</comments>
<description>&lt;p&gt;Fragment — это сущность, позволяющая инкапсулировать часть интерфейса и его логику для переиспользования. Фрагменты были представлены в Android 3.0 и позволили существенно упростить работу по проектированию интерфейса для устройств с различными размерами экранов (так называемая парадигма Responsive Design, когда одно и то же приложение выглядит по разному на устройствах с различной диагональю экрана).&lt;/p&gt;
&lt;p&gt;&lt;input id="button_using_fragment_without_ui" type="button" onclick="toggle(cut_using_fragment_without_ui, button_using_fragment_without_ui)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_using_fragment_without_ui" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Фрагменты очень удобны и обладают следующими основными свойствами:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Очень похожи на Activity и имеют схожий цикл жизни, при этом дополненный несколькими событиями;&lt;/li&gt;
&lt;li&gt;Как и Activity инкапсулируют логику работы с интерфейсом;&lt;/li&gt;
&lt;li&gt;Могут жить только внутри Activity, при этом один и тот же фрагмент может быть использован в различных Activity, что существенно упрощает задачу при проектировании интерфейса для различных устройств.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Фрагменты обладают ещё одним свойством, которое может быть весьма полезно при разработке приложений — фрагмент можно создать вовсе без пользовательского интерфейса и заставить его не умирать при пересоздании родительской Activity. Это свойство может быть очень к месту для хранения источника данных (модели) в таком фрагменте.&lt;/p&gt;
&lt;p&gt;Для многих начинающих разработчиков, пришедших с десктопных платформ, может стать настоящим откровением поведение Android например при изменении ориентации устройства — в таком случае Activity будет уничтожена и создана вновь с лэндскейпным лэйаутом. Соответственно если к Activity привязан какой-либо источник данных — он будет так же пересоздан (или перезагружен, в случае ContentProvider).&lt;/p&gt;
&lt;p&gt;Чтобы этого не произошло можно создать специальный фрагмент без UI и передать в его конструкторе значение true в сеттер setRetainInstance (Java), либо установить значение true свойству RetainInstance (C#). Далее инициализировать и сохранить в нём нашу модель.&lt;/p&gt;
&lt;p&gt;Все, после подобных манипуляций получим постоянный доступ к модели с сохранением её состояния даже при пересоздании родительской Activity.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Ниже небольшой пример кода на C# и Xamarin:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Фрагмент без интерфейса, будет хранить нашу модель
/// (данный фрагмент будет ретейниться после уничтожения основной вьюхи и наши данные не будут перегенериваться при пересоздании Activity)
/// &amp;lt;/summary&amp;gt;
public class ModelHolderFragment : Android.App.Fragment{
	public static string SearchTag = &amp;quot;MODEL_HOLDER_FRAGMENT&amp;quot;;

	public ViewPagerItem[] DataSourceItems { get; private set;}

	public override void OnCreate (Bundle savedInstanceState)
	{
		base.OnCreate (savedInstanceState);

		DataSourceItems = GenViewPagerItems ();

		//установим флаг, который запретит фрагменту умирать при уничтожении основной вьюхи
		RetainInstance = true;
	}

	/// &amp;lt;summary&amp;gt;
	/// Генерация фейковых данных
	/// &amp;lt;/summary&amp;gt;
	/// &amp;lt;returns&amp;gt;The view pager items.&amp;lt;/returns&amp;gt;
	private ViewPagerItem[] GenViewPagerItems(){
		...
		return items;
	}
}

///Хэндлер события OnCreate родительской Activity - тут создадим экземпляр нашего фрагмента
protected override void OnCreate(Bundle savedInstanceState) {
	base.OnCreate(savedInstanceState);
	SetContentView (Resource.Layout.activity_main);

	//инициализируем фрагмент без интерфейса для хранения модели только при первом создании вьюхи
	if (savedInstanceState == null) {
		FragmentManager.BeginTransaction ().Add ((Android.App.Fragment)new ModelHolderFragment (), ModelHolderFragment.SearchTag).Commit();
		FragmentManager.ExecutePendingTransactions ();
	}
	_model = (ModelHolderFragment)FragmentManager.FindFragmentByTag (ModelHolderFragment.SearchTag);
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
<pubDate>Fri, 19 Jun 2015 21:01:03 +0300</pubDate>
</item>

<item>
<title>Использование паттерна ViewHolder</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</guid>
<link>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</link>
<comments>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</comments>
<description>&lt;p&gt;С этой статьи я бы хотел начать цикл кратких заметок о разработке под мобильные устройства. Приложения под телефоны и планшеты — мое давнее увлечение. К сожалению плотно заняться которым, в силу различных обстоятельств, я смог только сейчас. В прочем хватит лирики, начнём.
В качестве платформы я выбрал Android, как наиболее массовую и открытую систему. В качестве языка программирования сегодня будем использовать C#, писать будем на Xamarin с использованием Android API.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_using_viewholder" type="button" onclick="toggle(cut_using_viewholder, button_using_viewholder)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_using_viewholder" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Практически любое мобильное приложение это некий экран с набором однотипных информационных элементов, для отображения которых обычно используется список или какой-либо грид. Из-за того, что платформа мобильная и работает на устройстве с небольшим объёмом памяти и слабым процессором (хаха! В моем первом компьютере было памяти раз в 15 меньше и процессор существенно слабее, чем сейчас в среднем телефоне) операционная система вынуждена ресурсы экономить. Соответственно если речь идёт о списке (ListView в Android), а если точнее об адаптере, который предоставляет данные для списка (например ArrayAdapter в Android) мы сталкиваемся с одной из особенностей, вызванных необходимостью беречь ресурсы.&lt;/p&gt;
&lt;p&gt;Такая особенность заключается в том, что лист не создаёт ячейки под каждую из записей подключенного источника данных. Он создаёт лишь то количество ячеек, сколько может уместиться на экране в данный момент плюс по одной лишней ячейке сверху и снизу. Такое поведение характерно не только для Android, но и для iOS.&lt;/p&gt;
&lt;p&gt;При этом когда пользователь прокручивает список система не удаляет ячейки, она старается их переиспользовать.&lt;/p&gt;
&lt;p&gt;Когда ячейка пропадает из поля видимости пользователя она передаётся в метод GetView() адаптера, в котором происходит заполнение ячейки данными. В этом методе можно решить, переиспользовать уже готовую ячейку или создать новую.&lt;/p&gt;
&lt;p&gt;Все бы ничего, но если лэйаут ячейки сложный то мы напрямую сталкиваемся с необходимостью проводить поиск элементов в лэйауте (TextView, ImageView и т. п.) для заполнения их актуальными данными. Вроде бы ничего страшного, вызвал &lt;i&gt;FindViewById()&lt;/i&gt; с нужным id элемента, получил его и нет проблем, но выполнение метода &lt;i&gt;FindViewById()&lt;/i&gt; это длительная операция, потому что она тянет за собой траверс по XML-коду лэйаута нашей ячейки. В итоге на сложных лэйаутах имеем просадку по производительности скроллинга списка.&lt;/p&gt;
&lt;p&gt;Чтобы решить данную проблему нам необходимо использовать паттерн &lt;b&gt;ViewHolder&lt;/b&gt;. Кратко его суть заключается в следующем:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Реализуем класс, который в конструкторе принимает нашу &lt;i&gt;View&lt;/i&gt;, делает траверс и ищет все её элементы, которые нам потребуются для заполнения данных;&lt;/li&gt;
&lt;li&gt;Записываем ссылки на эти элементы в соответствующие поля класса, снабжаем их геттерами, чтобы потом к ним обращаться;&lt;/li&gt;
&lt;li&gt;При создании нашей &lt;i&gt;View&lt;/i&gt; — создаём наш &lt;i&gt;класс-ViewHolder&lt;/i&gt; и записываем ссылку на него в свойстве &lt;i&gt;Tag&lt;/i&gt; у нашей &lt;i&gt;View&lt;/i&gt;;&lt;/li&gt;
&lt;li&gt;Теперь при попытке переиспользовать &lt;i&gt;View&lt;/i&gt; нам не нужно делать повторный поиск элементов в лэйауте, достаточно получить ссылку на &lt;i&gt;ViewHolder&lt;/i&gt; из свойства &lt;i&gt;Tag&lt;/i&gt; и обратиться к соответствующим геттерам холдера;&lt;/li&gt;
&lt;li&gt;PROFIT.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если делать реализацию на Java сложностей возникнуть не должно, но если использовать C# и Xamarin мы столкнемся с корнями, растущими из Java :-)&lt;/p&gt;
&lt;p&gt;Предположим есть такой класс:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class MyViewHolder{
public TextView MyTextView {get; private set;}

public MyViewHolder(View view){
...
}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Если теперь попытаться присвоить ссылку на него свойству &lt;i&gt;Tag&lt;/i&gt; нашей &lt;i&gt;View&lt;/i&gt; у нас ничего не получится. Поскольку &lt;i&gt;Tag&lt;/i&gt; принимает объект типа &lt;i&gt;Java.Lang.Object&lt;/i&gt;, а наш класс наследован от типа &lt;i&gt;Object&lt;/i&gt;. Прямое кастование к нужному типу так же не приведёт к успеху. Кастование сначала к &lt;i&gt;Object&lt;/i&gt;, а потом к &lt;i&gt;Java.Lang.Object&lt;/i&gt; не даст ошибки компиляции, зато упадёт в рантайме.&lt;/p&gt;
&lt;p&gt;Что же делать? &lt;b&gt;Все просто :-) наследовать наш класс от типа &lt;i&gt;Java.Lang.Object&lt;/i&gt; и будет счастье!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Небольшой пример того, как все это работает:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class TimetableItemAdapter : ArrayAdapter&amp;lt;TimetableItem&amp;gt;
{
	/// &amp;lt;summary&amp;gt;
	/// Класс ViewHolder, нужен что бы исключить множественный траверс по элементам вьюхи, для производительности
	/// &amp;lt;/summary&amp;gt;
	public class TimetableItemViewHolder : Java.Lang.Object{
		public TextView TimeText { get; private set;}
		public TextView DescriptionText { get; private set;}
		public ImageView AcceptButton { get; private set;}

		public TimetableItem BoundedItem { get; set;}

		public TimetableItemViewHolder(View view, TimetableItem item){
			TimeText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_time_text);
			DescriptionText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_description_text);
			AcceptButton = view.FindViewById&amp;lt;ImageView&amp;gt;(Resource.Id.timetable_accept);

			BoundedItem = item;
		}
	}

	private MainActivity _context;

	public TimetableItemAdapter(Context context, int resource, IList&amp;lt;TimetableItem&amp;gt; objects) : base(context, resource, objects) {
		_context = (MainActivity)context;
	}
			
	public override View GetView(int position, View convertView, ViewGroup parent) {
		View resultView = null;
		TimetableItemViewHolder holder = null;
		TimetableItem item = GetItem(position);

		if(convertView != null){
			resultView = convertView;
			holder = (TimetableItemViewHolder)convertView.Tag;
			holder.BoundedItem = item;
		}else{
			resultView = ((LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService))
				.Inflate(Resource.Layout.timetable_list_item, parent, false);
			holder = new TimetableItemViewHolder(resultView, item);
			resultView.Tag = holder;
		}

		if(item != null &amp;amp;&amp;amp; holder != null){
			holder.TimeText.Text = item.Time;
			holder.DescriptionText.Text = item.Description;
		}

		return resultView;
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Во &lt;i&gt;ViewHolder&lt;/i&gt; не обязательно хранить только ссылки на элементы лэйаута, иногда может потребоваться хранить и какие-либо сопутствующие данные. Например в коде выше во &lt;i&gt;ViewHolder&lt;/i&gt; записывается дополнительно ссылка на элемент из источника данных, для последующего использования где-нибудь. Но в данном случае нужно помнить, что при переиспользовании &lt;i&gt;View&lt;/i&gt; нужно не забыть записать в свойство ссылку на новый элемент источника данных.&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Wed, 17 Jun 2015 10:56:43 +0300</pubDate>
</item>

<item>
<title>Универсальный импортировщик заявок на стеклопакеты</title>
<guid isPermaLink="true">http://bibobo.ru/all/universalny-importirovschik-zayavok-na-steklopakety/</guid>
<link>http://bibobo.ru/all/universalny-importirovschik-zayavok-na-steklopakety/</link>
<comments>http://bibobo.ru/all/universalny-importirovschik-zayavok-na-steklopakety/</comments>
<description>&lt;p&gt;Одной из задач последних двух месяцев было написание универсального средства для импорта заявок на стеклопакеты (далее СП). Почему нужно именно универсальное средство и в чем его универсальность, спросите вы.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Для любого крупного производителя СП рано или поздно встает вопрос об автоматизированном приеме заявок от клиентов. Поток заказов (заявок) растет, вносить их вручную становится экономически не выгодно. Очень крупные предприятия (например СТИС) могут себе позволить разработать собственный формат заявки и заставить своих клиентов использовать только его, но заводы поменьше не могут себе позволить такую роскошь — им приходится принимать заявки в том формате, в котором хочет клиент. На такие предприятия и рассчитывалось данное универсальное средство импорта.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_application_sp_import" type="button" onclick="toggle(cut_application_sp_import, button_application_sp_import)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_application_sp_import" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Формат заявки может очень сильно отличаться, наиболее распространенным естественно является обычный Excel, некоторые клиенты отправляют заявку в формате CSV. Самые суровые формируют заявку в WORD или просто пишут на бумажке, сканируют и отправляют по электронной почте.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;На данный момент средство импорта имеет поддержку Excel, CSV и Word, но в данной статье мы опишем только импорт из Excel и CSV. Поддержку Word добавлял мой коллега и друг &lt;a href="http://drmiller-developer.blogspot.ru/" target="top"&gt;Евгений Мельник&lt;/a&gt;, если он захочет — расскажет про этот вид импорта.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Итак основные задачи, которые ставились перед нами:&lt;/p&gt;
&lt;p&gt;—Разработать архитектуру, предоставляющую инфраструктуру для импорта заявок;&lt;br /&gt;
—Должна быть возможность легко добавлять новые форматы файлов для импорта без правки архитектуры, простой реализацией собственных классов;&lt;br /&gt;
—Должна быть возможность создавать шаблоны для импорта под каждого конкретного контрагента;&lt;br /&gt;
—Средство импорта должно преобразовывать любой бред, написанный контрагентом в заявке в понятные основному ПП данные, это в первую очередь касается формулы СП (об этом поговорим отдельно);&lt;br /&gt;
—Создать интерфейс, позволяющий выбрать соответствующий шаблон и импортировать заявку;&lt;br /&gt;
—Добавить административный интерфейс, позволяющий создавать, редактировать и удалять шаблоны заявок;&lt;br /&gt;
—Предоставить возможность разграничить права на интерфейсы пользователя;&lt;br /&gt;
—Полная интеграция с основным ПП нашей компании — winDraw;&lt;br /&gt;
—Используем C# 2.0 и компоненты DevExpress 10.2.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;Говоря о шаблонах для каждого контрагента и преобразовании его данных в данные, понятные основному ПП нужно понимать одну особенность производства СП.&lt;/p&gt;
&lt;p&gt;Видов стеклопакетов существует огромное количество, и речь идет не только об их форме, но в первую очередь об их &lt;b&gt;формуле&lt;/b&gt;. Пакет может быть однокамерным (состоять из 2-х стекол и одной рамки), а может быть двухкамерным (состоять из 3 стекол и двух рамок). Также может сильно отличаться тип стекол в пакете, это может быть просто 4-мм стекол, а может быть и 5-мм, 6-мм. Стекло может быть закаленным, армированным, это может быть триплекс (состоит из трех стекол, склеенных между собой). На стекло может быть наклеена пленка, бронировочная или просто цветная. Она может быть наклеена только на 1-е стекло, а может и на оба. Размер камеры (толщина дистанционной рамки) может существенно различаться. В камеру может быть закачан аргон или другой газ.&lt;/p&gt;
&lt;p&gt;Как вы видите нюансов очень много. Специально, что бы установить хоть какую-то систему во всем этом многообразии был разработан ГОСТ, описывающий СП в целом и маркировку СП в частности &lt;a href="https://www.google.ru/search?q=%D0%93%D0%9E%D0%A1%D0%A2+24866-99"&gt;ГОСТ 24866-99&lt;/a&gt;. Но большинство клиентов не стремятся использовать стандарт, им просто нужно получить свои стеклопакеты. И тут начинается самое интересное. Клиент пишет вместо стандартного разделителя «x» тире «-» или пробел. Либо просто пишет в заявке «Двухкамерный СП из стекла 4мм». Описываемые клиентом маркировки стекол могут существенно отличаться от маркировок, указанных в программе.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Поскольку предусмотреть все варианты написания того или иного стекла или рамки физически невозможно, было принято решение — для каждого клиента создать таблицу замен его написания на наше. Но, если использовать таблицу замен может возникнуть проблема литерал &lt;b&gt;AR&lt;/b&gt;, описывающий аргон  легко может &lt;i&gt;поломать&lt;/i&gt; строку &lt;b&gt;HP CARBON 05&lt;/b&gt;, которая описывает пленку. Т.е, что бы таблица замен работала корректно нужно производить поиск и замену сначала самых длинных литералов, а затем более коротких. Необходимо просто вычислить длину строки при добавлении и хранить ее рядом с литералом для замены, тогда определение порядка просмотра таблицы становится очевидным.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Простая заявка на СП выглядит следующим образом:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/----.png" width="968" height="445" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Если проанализировать ее формат становится ясно, что состоит она из 2-х частей:&lt;/p&gt;
&lt;p&gt;—Заголовочная часть, которая содержит основную информацию о компании-клиенте, номере заявки, дате отправки и желаемой дате получения стеклопакетов;&lt;br /&gt;
—Табличной части с формулами СП, их размерами и количеством.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;Стало ясно, что наиболее простым внутренним представлением данных будем именно табличный формат, используем &lt;b&gt;DataTable&lt;/b&gt; как основное хранилище исходных данных&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Была разработана следующая архитектура, рассмотрим ее подробнее:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/ClassDiagramSpApplicationImport.png" width="715" height="361" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Основу всего составляют 3 интерфейса:&lt;/p&gt;
&lt;p&gt;—&lt;b&gt;IDataProvider&lt;/b&gt; — этот интерфейс реализуют классы, которые предоставляют данные для импорта. Т. е. преобразуют исходный файл в понятную ядру таблицу;&lt;br /&gt;
—&lt;b&gt;IDataParser&lt;T&gt;&lt;/b&gt; — интерфейс, который позволит реализовать классы, преобразующие клиентский &lt;i&gt;бред&lt;/i&gt; в понятные нам данные (организует работу с таблицей замен, преобразует строки в числа, разложит формат габаритных размеров СП в понятные нам два числа);&lt;br /&gt;
—&lt;b&gt;IXmlSerializable&lt;/b&gt; — нужен для сериализации/десериализации шаблонов клиентов.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;Рассмотрим код наших интерфейсов:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;interface IDataParser&amp;lt;T&amp;gt;
{
    T Parse(string val, DocumentTemplate template);
}
...
interface IDataProvider
{
     /// &amp;lt;summary&amp;gt;
    /// Возвращает тип провайдера (Excel, CSV, Word и т.д.)
    /// &amp;lt;/summary&amp;gt;
    string ProviderType { get; }

    /// &amp;lt;summary&amp;gt;
    /// Необходимо задавать фильтр расширений для файла, загружаемого провайдером
    /// &amp;lt;/summary&amp;gt;
    string FileExtensionFilter { get; }

    /// &amp;lt;summary&amp;gt;
    /// Загружает данные и возвращает таблицу для разбора
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;Таблица для разбора&amp;lt;/returns&amp;gt;
    DataTable LoadData();
}
...
interface IXmlSerializable
{
    XmlElement Serialize(XmlDocument doc);
    bool Deserialize(XmlElement element);
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Все выглядит очень просто. Стоит отдельно сказать про класс &lt;i&gt;DocumentTemplate&lt;/i&gt;, это как раз то место, в котором хранится вся информация о шаблоне под конкретного клиента, а именно:&lt;/p&gt;
&lt;p&gt;—Какой формат используется для загрузки этого шаблона (какой провайдер данных);&lt;br /&gt;
—С какой строки начинается табличная часть;&lt;br /&gt;
—Какую колонку нужно анализировать в табличной части, что бы понять, что она закончилась (просто смотрит, если в колонке нет данных — значит табличная часть закончилась);&lt;br /&gt;
—Хранит список экземпляров класса &lt;i&gt;TemplateField&lt;/i&gt; для табличной части и заголовочной части (&lt;i&gt;TemplateField&lt;/i&gt; описывает конкретное поле в таблице, его номер строки и колонки, внутреннее имя, содержит описание);&lt;br /&gt;
—Хранит таблицу замен для преобразования формул СП клиента в формулы СП основной программы;&lt;br /&gt;
—Хранит объект типа &lt;i&gt;CustomerInformation&lt;/i&gt;, описывающий данные клиента (id клиента, его наименование), для подстановки в сформированный заказ после импорта;&lt;br /&gt;
—Реализует методы интерфейса &lt;i&gt;IXmlSerializable&lt;/i&gt; для сериализации/десериализации.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;Шаблон сам по себе ничего не делает, он просто хранит данные. Сердцем всего является класс &lt;i&gt;DataLoader&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Код данного класса особо ничем не примечателен, за исключением кода загрузки заявки по выбранному шаблону:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Загружает заявку по выбранному шаблону
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;template&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
private void LoadTemplate(DocumentTemplate template)
{
    if(template == null) return;

    //выберем провайдер и инициализируем его
    IDataProvider provider = null;
    if (template.Type == &amp;quot;Excel&amp;quot;) provider = new ExcelDataProvider();
    if (template.Type == &amp;quot;Word&amp;quot;) provider = new WordDataProvider();

    if (provider == null)
    {
        throw new ApplicationException(&amp;quot;Ошибка при поиске провайдера данных для шаблона с типом \&amp;quot;&amp;quot; + template.Type + &amp;quot;\&amp;quot;&amp;quot;);
    }

    //загрузим данные через провайдер
    DataTable dtData = provider.LoadData();
    if (dtData == null)
    {
        throw new ApplicationException(&amp;quot;Ошибка при загрузке данных из файла&amp;quot;);
    }

    //получим массивы заголовочных полей и табличных полей, что бы найти данные в таблице, предоставленной провайдером
    DocumentTemplate.TemplateField[] headerFields = template.HeaderFields;
    DocumentTemplate.TemplateField[] tableFields = template.TableField;

    //инициализируем класс, который работает с заказом winDraw, вся спицифичная кухня, касающаяся winDraw происходит внутри
    OrderData orderData = new OrderData();

    //далее используя шаблон и таблицу от провайдера будем заполнять данные в OrderData
    if (template.Customer != null)
    {
        orderData.CustomerInformation = template.Customer.Clone();
    }

    //сначала вытащим все данные из шапки
    foreach (DocumentTemplate.TemplateField field in headerFields)
    {
        switch (field.Name)
        {
            case &amp;quot;customer&amp;quot;:
                orderData.Customer = GetHeaderStringData(field, dtData);
                break;

            case &amp;quot;shipmentAddress&amp;quot;:
                orderData.ShipmentAddress = GetHeaderStringData(field, dtData);
                break;

            case &amp;quot;comment&amp;quot;:
                orderData.Comment = GetHeaderStringData(field, dtData);
                break;

            case &amp;quot;dateApplication&amp;quot;:
                orderData.DateApplication = GetHeaderStringData(field, dtData);
                break;

            case &amp;quot;dateShipment&amp;quot;:
                orderData.DateShipment = GetHeaderStringData(field, dtData);
                break;
        }
    }

    //теперь вытащим все данные из табличной части
    for (int idx = template.TablePartStartRow; idx &amp;lt; dtData.Rows.Count; idx++)
    {
        DataRow currentRow = dtData.Rows[idx];
        //условие прекращения работы цикла, когда данные в таблице закончились
        if(currentRow[template.TablePartEndColumn] == DBNull.Value || string.IsNullOrEmpty(currentRow[template.TablePartEndColumn].ToString())) break;

        OrderData.OrderPositionData position = orderData.AddNewPosition();

        DocumentTemplate.TemplateField fieldWidth = null;
        DocumentTemplate.TemplateField fieldHeight = null;

        foreach (DocumentTemplate.TemplateField field in tableFields)
        {
            switch (field.Name)
            {
                case &amp;quot;formula&amp;quot;:
                    position.Formula = GetTableFormulaData(field, currentRow, template);
                    break;

                case &amp;quot;width&amp;quot;:
                    fieldWidth = field;
                    break;

                case &amp;quot;height&amp;quot;:
                    fieldHeight = field;
                    break;

                case &amp;quot;qu&amp;quot;:
                    position.Qu = GetTableIntData(field, currentRow);
                    break;

                case &amp;quot;comment&amp;quot;:
                    position.Comment = GetTableStringData(field, currentRow);
                    break;

                case &amp;quot;orderName&amp;quot;:
                    position.OrderName = GetTableStringData(field, currentRow);
                    break;
            }
        }

        if(fieldWidth == null || fieldHeight == null) throw new ApplicationException(&amp;quot;Ошибка при загрузке полей Ширина, Высота&amp;quot;);

        //если колонка ширины равна колонке высоты - значит наш клиент пишет ширину и высоту через спец сепаратор :)
        if (fieldWidth.Column == fieldHeight.Column)
        {
            if (currentRow[fieldWidth.Column] != DBNull.Value)
            {
                string val = currentRow[fieldWidth.Column].ToString();

                //тут применен такой хитрый хак, из-за того, что для Word нужно более одного сепаратора при расчленении строки
                string[] separators = template.WidthHeightSeparator.Split(new char[] {'|'});
                string[] par = val.Split(separators,
                    StringSplitOptions.RemoveEmptyEntries);

                position.Width = _intParser.Parse(par[0], template);
                position.Height = _intParser.Parse(par[1], template);
            }
        }
        else
        {
            //иначе наш клиент молодец и пишет ширину/высоту в разных столбцах
            position.Width = GetTableIntData(fieldWidth, currentRow);
            position.Height = GetTableIntData(fieldHeight, currentRow);
        }
    }

    //в финале создаем заказ
    orderData.CreateOrder();
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Интерес так же могут вызвать методы:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;private string GetHeaderStringData(DocumentTemplate.TemplateField field, DataTable dtData)
private string GetTableStringData(DocumentTemplate.TemplateField field, DataRow currentRow)
private string GetTableFormulaData(DocumentTemplate.TemplateField field, DataRow currentRow, DocumentTemplate template)
private int GetTableIntData(DocumentTemplate.TemplateField field, DataRow currentRow)&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Это всего лишь обертки над классами-парсерами, реализующими интерфейс &lt;i&gt;IDataParser&lt;T&gt;&lt;/i&gt;. Рассмотрим их подробнее:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Строковый парсер
/// &amp;lt;/summary&amp;gt;
public class StringDataParser : IDataParser&amp;lt;string&amp;gt;
{
    public string Parse(string val, DocumentTemplate template)
    {
        return val.Trim();
    }
}

/// &amp;lt;summary&amp;gt;
/// Парсер чисел
/// &amp;lt;/summary&amp;gt;
public class IntDataParser : IDataParser&amp;lt;int&amp;gt;
{
    public int Parse(string val, DocumentTemplate template)
    {
        return Convert.ToInt32(GetOnlyNumericInformation(val));
    }

    /// &amp;lt;summary&amp;gt;
    /// Оставляет только ту информацию, которая относится к числовой (числа, знаки , и .)
    /// Так же исключает сливание вместе двух чисел, например:
    /// ,113,33 443 - будет равно 133,33
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;val&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    private string GetOnlyNumericInformation(string val)
    {
        val = val.Trim();
        bool digitsStarted = false;

        StringBuilder sb = new StringBuilder();
        for (int idx = 0; idx &amp;lt; val.Length; idx++)
        {
            char c = val[idx];

            if (IsNumericChar(c))
            {
                if (!digitsStarted) digitsStarted = true;
                sb.Append(c);
            }
            else
            {
                if(digitsStarted) break;
            }
        }

        string result = sb.ToString().Trim(',').Trim('.');
        return result.Replace(',', '.');
    }

    /// &amp;lt;summary&amp;gt;
    /// Свой вариант метода char.IsDigit, считает числовым еще и символы: . ,
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;c&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    private bool IsNumericChar(char c)
    {
        return char.IsDigit(c) || c == ',' || c == '.';
    }
}

/// &amp;lt;summary&amp;gt;
/// Парсер формулы СП
/// &amp;lt;/summary&amp;gt;
public class FormulaDataParser : IDataParser&amp;lt;string&amp;gt;
{
    public string Parse(string val, DocumentTemplate template)
    {
        foreach (DataRow dr in template.DtChanges.Select(&amp;quot;&amp;quot;, &amp;quot;complexity DESC&amp;quot;))
        {
            string searchString = dr[&amp;quot;search&amp;quot;].ToString();
            string replaceString = dr[&amp;quot;replace&amp;quot;].ToString();

            val = val.Replace(searchString, replaceString);
        }

        return val;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Самое интересное происходит в классе &lt;i&gt;FormulaDataParser&lt;/i&gt;, тут мы видим как преобразовать формулу СП клиента в понятную нам формулу, используя таблицу замен. При этом в поле &lt;i&gt;complexity&lt;/i&gt; хранится длина исходной строки, что бы организовать последовательность просмотра таблицы в правильном порядке&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;И в конце хочется показать что же получилось, как выглядит интерфейс пользователя:&lt;/p&gt;
&lt;p&gt;Окно, которое видит менеджер. Можно выбрать шаблон заявки и импортировать ее&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/SpAppImportMainWindow.png" width="329" height="121" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Окно, которое видит администратор по конкретной заявке. Можно настроить все параметры полей (заголовочных и табличных), а так же заполнить таблицу замен&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/SpAppImportSettingsWindow.png" width="698" height="465" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
<pubDate>Sat, 07 Feb 2015 15:03:25 +0300</pubDate>
</item>

<item>
<title>Реализация механизма плагинов в программе</title>
<guid isPermaLink="true">http://bibobo.ru/all/realizaciya-mehanizma-plaginov-v-prilozhenii/</guid>
<link>http://bibobo.ru/all/realizaciya-mehanizma-plaginov-v-prilozhenii/</link>
<comments>http://bibobo.ru/all/realizaciya-mehanizma-plaginov-v-prilozhenii/</comments>
<description>&lt;p&gt;Рано или поздно практически любой разработчик популярной программы сталкивается с необходимостью реализации возможности написания плагинов для нее. Лучше, если данная возможность заложена изначально. Наиболее простым путем в реализации такого функционала является создание специального интерфейса (&lt;i&gt;например IPlugin&lt;/i&gt;). Данный интерфейс может содержать все необходимые методы и свойства для класса, работающего в инфраструктуре плагинов. Позже программа при загрузке сборки с плагином будет искать все классы, реализующие этот интерфейс, инстанциировать их как плагины и отображать их список в интерфейсе пользователя.&lt;/p&gt;
&lt;p&gt;В основе работы всей инфраструктуры плагинов лежит такая возможность Microsoft .NET Framework как Рефлексия (пространство имен &lt;i&gt;System.Reflection&lt;/i&gt;, более подробно &lt;a href="http://msdn.microsoft.com/ru-ru/library/System.Reflection(v=vs.110).aspx"&gt;можно почитать на MSDN&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;input id="button_plugins_machanism" type="button" onclick="toggle(cut_plugins_machanism, button_plugins_machanism)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_plugins_machanism" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;У меня есть векторный редактор, который имеет три типа «кистей»:&lt;/p&gt;
&lt;p&gt;— Кисти рисования&lt;br /&gt;
— Кисти для редактирования уже нарисованных примитивов (обрезка линий, удлинение линий и т. п.)&lt;br /&gt;
— Кисти для реализации расчетного функционала (простой пример — посчитать длины определенных элементов на чертеже)&lt;br /&gt;
Будем использовать его как пример реализации инфраструктуры для работы плагинов.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;В статье рассмотрим инфраструктуру плагинов, написанную для этого редактора, она будет состоять из:&lt;/p&gt;
&lt;p&gt;— Менеджера плагинов (реализует весь механизм загрузки и инстанциации плагинов)&lt;br /&gt;
— Набора специальных атрибутов (позволят добавить в метаданные дополнительную информацию о том или ином плагине)&lt;br /&gt;
— Несколько интерфейсов, позволяющих писать плагины к различному функционалу редактора&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Первым шагом необходимо реализовать интерфейс для каждого типа кисти (&lt;i&gt;исходные интерфейсы в редакторе довольно сложны, имеют много методов и свойств, в примере они намеренно упрощены&lt;/i&gt;)&lt;/p&gt;
&lt;p&gt;Обычно простейший интерфейс для плагина может выглядеть следующим образом:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public interface IPlugin
{
	//Возвращает наименование плагина
	string Name { get; }

	//Инициализирует плагин
	void Init();

	//Метод, который собственно делает всю работу плагина
	void DoWork();
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Из кода становится ясно, что после инстанциации плагинов достаточно получить имя каждого (&lt;i&gt;поле Name&lt;/i&gt;), вывести этот список в интерфейс. После чего когда пользователь выберет определенный плагин — вызвать его метод &lt;i&gt;Init()&lt;/i&gt;, который инициализирует его и затем вызвать метод &lt;i&gt;DoWork()&lt;/i&gt;, который сделает уже работу плагина.&lt;/p&gt;
&lt;p&gt;Используя такой интерфейс программе абсолютно по барабану что именно делает плагин, главное что она знает порядок вызова методов. Такой подход позволяет существенно расширить функционал исходной программы. Но вернемся к редактору.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Интерфейс, описывающий кисть рисования. Содержит основные методы, позволяющие клонировать кисть, отрисовать примитив кисти, инициализировать новый экземпляр кисти. Так же присутствует свойство-геттер, возвращающее строковый идентификатор кисти.&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public interface IDrawObject
{
	//Возвращает строковый ID кисти
	string Id { get; }
	
	//Клонирует кисть
	IDrawObject Clone();

	//Отрисовывает примитив кисти
	void Draw(ICanvas canvas, RectangleF unitrect);

	//Инициализирует новый экземпляр кисти
	void InitializeFromModel(UnitPoint point, DrawingLayer layer, ISnapPoint snap)
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Интерфейс, описывающий кисть редактирования. Содержит методы клонирования объекта и обработки нажатия мыши по экрану (позволяет реализовать логику работы кисти редактирования через последовательное выделение объектов, например выделили базовый объект, обрезали второй объект по базовому).&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public interface IEditTool
{
        //Возвращает строковый ID кисти
        string Id { get; }

        //Клонирует кисть
	IEditTool Clone();

        //Реализация логики кисти, обработка нажатия на экран
	public DrawObjectMouseDown OnMouseDown(ICanvas canvas, UnitPoint point, ISnapPoint snappoint)
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;
Интерфейс, описывающий расчетную кисть. Содержит методы, позволяющие клонировать кисть, а так же выполнить расчетный алгоритм, реализуемый кистью.
&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public interface ICalcTool
{
        //Возвращает строковый ID кисти
        string Id { get; }

        //Клонирует кисть
        ICalcTool Clone();

        //Запускает расчетный алгоритм кисти
        void PerformCalc(IModel model);
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;
Следующим этапом для каждого типа кисти необходимо реализовать атрибут, позволяющий добавить дополнительную информацию к кисти (ее имя, описание, имя файла иконки, хоткей для запуска и т. п.). Пример для кисти рисования:
&lt;/p&gt;
&lt;p&gt;&lt;input type="button" onclick="toggle(code1_realizaciya_mehanizma_plaginov_v_prilozhenii)" value="Нажмите, чтобы отобразить или спрятать код"&gt;&lt;/p&gt;
&lt;pre id="code1_realizaciya_mehanizma_plaginov_v_prilozhenii" style="display: none;"&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Атрибут, описывающий графический объект
/// &amp;lt;/summary&amp;gt;
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class DrawToolAttribute : Attribute
{
        /// &amp;lt;summary&amp;gt;
        /// Имя кисти рисования
        /// &amp;lt;/summary&amp;gt;
        public string Name { get; private set; }
        /// &amp;lt;summary&amp;gt;
        /// Описание кисти рисования
        /// &amp;lt;/summary&amp;gt;
        public string Description { get; private set; }
        /// &amp;lt;summary&amp;gt;
        /// Изображение для кнопки кисти рисования
        /// &amp;lt;/summary&amp;gt;
        public string GlyphFileName { get; private set; }
        /// &amp;lt;summary&amp;gt;
        /// Горячая клавиша
        /// &amp;lt;/summary&amp;gt;
        public Keys Shortcut { get; private set; }

        public DrawToolAttribute()
        {
            Name = string.Empty;
            Description = string.Empty;
            GlyphFileName = string.Empty;
            Shortcut = Keys.None;
        }

        /// &amp;lt;summary&amp;gt;
        /// В конструкторе передаем все необходимые параметры для описания кисти
        /// &amp;lt;/summary&amp;gt;
        public DrawToolAttribute(string name, string description, string glyphFileName, Keys shortcut)
        {
            if (name == null) throw new ArgumentNullException(&amp;quot;name&amp;quot;);
            if (description == null) throw new ArgumentNullException(&amp;quot;description&amp;quot;);
            if (glyphFileName == null) throw new ArgumentNullException(&amp;quot;glyphFileName&amp;quot;);

            Name = name;
            Description = description;
            GlyphFileName = glyphFileName;
            Shortcut = shortcut;
        }
}

//Аналогичным способом реализованы атрибуты для кисти редактирования и кисти расчета:
public class EditToolAttribute : Attribute
{
...
}

public class CalcToolAttribute : Attribute
{
...
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Перед определением заголовка класса атрибута использован специализированный атрибут &lt;i&gt;AttributeUsage&lt;/i&gt;, который позволяет описать возможные применения создаваемого атрибута, в нашем случае атрибуты должны применяться только к классу. Так же устанавливается свойство &lt;i&gt;Inherited&lt;/i&gt; в значение &lt;i&gt;false&lt;/i&gt;, которое запрещает наследование атрибута производными классами (если имеется базовый класс, к которому применен наш атрибут, то при наследовании от этого класса наш атрибут наследоваться не будет).&lt;/p&gt;
&lt;p&gt;Так же стоит знать, что любой новый атрибут должен наследоваться от класса &lt;i&gt;Attribute&lt;/i&gt;, это является обязательным условием.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Ниже пример использования нашего атрибута при создании плагина с кистью рисования «Креста»&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;[DrawTool(&amp;quot;Крест&amp;quot;, &amp;quot;Инструмент рисования креста&amp;quot;, &amp;quot;cross_tool.png&amp;quot;, (System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.R))]
class Cross : IDrawObject
{
...
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&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;— Последовательно загружать сборки (с расширением dll) с диска&lt;br /&gt;
— Для каждой сборки получать все типы, находящиеся в этой сборке&lt;br /&gt;
— Для каждого типа получить список его кастомных атрибутов&lt;br /&gt;
— Проверить, реализует ли тип один из интерфейсов плагинов (&lt;i&gt;IDrawObject, IEditTool, ICalcTool&lt;/i&gt;)&lt;br /&gt;
— Проверить, имеет ли тип один из атрибутов, описывающих кисть&lt;br /&gt;
— Если описанные выше условия выполняются — то необходимо инстанциировать кисть и добавить ее в соответствующий список.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;В работе всего этого хозяйства будут использоваться классы из пространства имен &lt;i&gt;System.Reflection&lt;/i&gt;, а именно следующие:&lt;/p&gt;
&lt;p&gt;— &lt;a href="http://msdn.microsoft.com/ru-ru/library/System.Reflection.Assembly(v=vs.110).aspx"&gt;Assembly&lt;/a&gt;, представляет саму сборку&lt;br /&gt;
— &lt;a href="http://msdn.microsoft.com/ru-ru/library/System.Activator(v=vs.110).aspx"&gt;Activator&lt;/a&gt;, предоставляет возможность инстанциировать экземпляр класса&lt;br /&gt;
— &lt;a href="http://msdn.microsoft.com/ru-ru/library/System.Reflection.MemberInfo(v=vs.110).aspx"&gt;MemberInfo&lt;/a&gt;, предоставляет сведения об атрибутах компонента и дает доступ к его свойствам&lt;br /&gt;
— Так же используем метод &lt;a href="http://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&amp;l=RU-RU&amp;k=k(System.Type.IsAssignableFrom);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv3.5);k(DevLang-csharp)&amp;rd=true"&gt;IsAssignableFrom&lt;/a&gt; класса &lt;i&gt;Type&lt;/i&gt;, который позволяет определить унаследован ли наш тип от другого типа&lt;br /&gt;
&lt;input type="button" onclick="toggle(code2_realizaciya_mehanizma_plaginov_v_prilozhenii)" value="Нажмите, чтобы отобразить или спрятать код"&gt;&lt;/p&gt;
&lt;pre id="code2_realizaciya_mehanizma_plaginov_v_prilozhenii" style="display: none;"&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;static class PluginManager
{
        /// &amp;lt;summary&amp;gt;
        /// Загружать ли плагины при старте
        /// &amp;lt;/summary&amp;gt;
        public static bool IsLoadPlugins = false;

        /// &amp;lt;summary&amp;gt;
        /// Возвращает список объектов рисования
        /// &amp;lt;/summary&amp;gt;
        public static IEnumerable&amp;lt;IDrawObject&amp;gt; DrawObjectInstances { get; private set; }
        /// &amp;lt;summary&amp;gt;
        /// Возвращает список кистей редактирования
        /// &amp;lt;/summary&amp;gt;
        public static IEnumerable&amp;lt;IEditTool&amp;gt; EditToolInstances { get; private set; }
        /// &amp;lt;summary&amp;gt;
        /// Возвращает список расчетных инструментов
        /// &amp;lt;/summary&amp;gt;
        public static IEnumerable&amp;lt;ICalcTool&amp;gt; CalcToolInstances { get; private set; }

        /// &amp;lt;summary&amp;gt;
        /// Флаг об успешной загрузке сборок плагинов
        /// &amp;lt;/summary&amp;gt;
        public static bool IsPluginsLoaded
        {
            get
            {
                if (_pluginCache == null || _pluginCache.Count == 0) return false;
                return true;
            }
        }

        /// &amp;lt;summary&amp;gt;
        /// Загружает все модули в словарь и возвращает его
        /// &amp;lt;/summary&amp;gt;
        public static bool LoadAllModules(string path)
        {
            InitCollections();

            if (!IsLoadPlugins)
            {
                return false;
            }

            if (string.IsNullOrEmpty(path)) return false;

            foreach (string fileName in Directory.GetFiles(path/*, &amp;quot;*.dll&amp;quot;*/))
            {
                Assembly asm = null;
                string asmPath = Path.Combine(path, fileName);
                bool isAsm = false;
                bool isPlugin = false;

                try
                {
                        asm = Assembly.LoadFile(asmPath);

                        foreach (Type type in asm.GetTypes())
                        {
                            foreach (Attribute attribute in type.GetCustomAttributes(typeof (Attribute), true))
                            {
                                //кисти рисования
                                if (
                                    //если тип поддерживает соответствующий атрибут
                                    attribute is DrawToolAttribute
                                    &amp;amp;&amp;amp;
                                    //и отвечает соответствующему интерфейсу
                                    typeof (IDrawObject).IsAssignableFrom(type)
                                    )
                                {
                                    //инстанциируем экземпляр кисти из плагина
                                    DrawObjectInstances.Add(Activator.CreateInstance(type) as IDrawObject);
                                    break;
                                }

                                //кисти редактирования
                                if (
                                    //если тип поддерживает соответствующий атрибут
                                    attribute is EditToolAttribute
                                    &amp;amp;&amp;amp;
                                    //и отвечает соответствующему интерфейсу
                                     typeof (IEditTool).IsAssignableFrom(type)
                                    )
                                {
                                    //инстанциируем экземпляр кисти из плагина
                                    EditToolInstances.Add(Activator.CreateInstance(type) as IDrawObject);
                                    break;
                                }

                                //расчетные кисти
                                if (
                                    //если тип поддерживает соответствующий атрибут
                                    attribute is CalcToolAttribute
                                    &amp;amp;&amp;amp;
                                    //и отвечает соответствующему интерфейсу
                                     typeof (ICalcTool).IsAssignableFrom(type)
                                    )
                                {
                                    //инстанциируем экземпляр кисти из плагина
                                    CalcToolInstances.Add(Activator.CreateInstance(type) as IDrawObject);
                                    break;
                                }
                            }
                        }
                }
                catch(ApplicationException ex)
                {
                    MessageBox.Show(&amp;quot;Ошибка при загрузке плагина из сборки &amp;quot; + fileName, &amp;quot;Ошибка&amp;quot;, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

            return true;
        }

        /// &amp;lt;summary&amp;gt;
        /// Инициализирует пустые коллекции элементов плагинов (необходимо для случая, когда редактор загружается с выключенной возможностью использования плагинов, просто инициализирует пустые коллекции кистей)
        /// &amp;lt;/summary&amp;gt;
        private static void InitCollections()
        {
            DrawObjectInstances = new List&amp;lt;IDrawObject&amp;gt;();
            EditToolInstances = new List&amp;lt;IEditTool&amp;gt;();
            CalcToolInstances = new List&amp;lt;ICalcTool&amp;gt;();
        }
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Вот собственно и все, что следовало написать по этой теме. Остается только вызвать метод &lt;i&gt;LoadAllModules()&lt;/i&gt; для загрузки плагинов и забрать свой плагин из соответствующей коллекции.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;В качестве завершения, из статьи мы получили следующую информацию:&lt;/p&gt;
&lt;p&gt;— Узнали, как построить простую (и не совсем простую) инфраструктуру плагинов для своей программы&lt;br /&gt;
— Рассмотрели реализацию простейшего интерфейса для плагина&lt;br /&gt;
— Узнали как реализовать собственный атрибут&lt;br /&gt;
— Разобрались с тем, как загружать сборки с диска, искать в них типы и получить список атрибутов этих типов&lt;br /&gt;
— Узнали как инстанциировать типы при помощи класса &lt;i&gt;Activator&lt;/i&gt;&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;В качестве домашней работы можно модифицировать код загрузки плагинов так, что бы он мог отделить управляемую сборку от неуправляемой, обработать такую ситуацию корректно и не упасть при этом :)&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Sun, 04 Jan 2015 23:31:19 +0300</pubDate>
</item>

<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>