Применение рефлексии, позднего связывания и атрибутов

20

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

Что именно подразумевается под расширяемым приложением? Рассмотрим IDE-среду Visual Studio 2010. При разработке в этом приложении были предусмотрены специальные "ловушки" (hook) для предоставления другим производителям ПО возможности подключать свои специальные модули. Понятно, что разработчики Visual Studio 2010 не могли добавить ссылки на несуществующие внешние сборки .NET (т.е. воспользоваться ранним связыванием), тогда как же им удалось обеспечить в приложении необходимые методы-ловушки? Ниже описан один из возможных способов решения этой проблемы.

Если расширяемое приложение изначально программируется так, чтобы запрашивать определенные интерфейсы, оно получает возможность определять во время выполнения, может ли активизироваться интересующий тип, и после успешного прохождения типом такой проверки позволять ему поддерживать дополнительные интерфейсы и получать доступ к их функциональным возможностям полиморфным образом. Именно такой подход и предприняли разработчики Visual Studio 2010, причем ничего особо сложного в нем нет.

В первую очередь необходимо создать сборку с типами, которые должна обязательно использовать каждая оснастка, чтобы иметь возможность подключаться к расширяемому приложению. Для этого создадим проект типа Class Library (Библиотека классов), и определим в нем два следующих типа:

using System;

namespace PW_CommonType
{
    public interface IApplicationFunc
    {
        void Go();
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class InfoAttribute : System.Attribute
    {
        public string CompanyName { get; set; }
        public string CompanyUrl { get; set; }
    }
}

Далее потребуется создать тип, реализующий интерфейс IApplicationFunc. Чтобы не усложнять пример создания расширяемого приложения, давайте сделаем этот тип простым. Создадим новый проект типа Class Library на C# и определим в нем тип класса по имени MyCompanyInfo:

using System;
using PW_CommonType;
using System.Windows.Forms;

namespace MyCompany
{
    [Info(CompanyName="ProfessorWeb", CompanyUrl="professorweb.ru")]
    public class MyCompanyInfo : IApplicationFunc
    {
        void IApplicationFunc.Go()
        {
            MessageBox.Show("Важная информация!");
        }
    }
}

И, наконец, последний шаг заключается в создании самого расширяемого приложения Windows Forms, которое позволит пользователю выбирать желаемую оснастку с помощью стандартного диалогового окна открытия файлов Windows.

Теперь нужно добавить в него ссылку на сборку PW_CommonType.dll, но не на библиотекy кода CompanyInfo.dll. Кроме того, необходимо импортировать в главный файл кода формы (для его открытия щелкните правой кнопкой мыши в визуальном конструкторе формы и выберите в контекстном меню пункт View Code (Просмотреть код)) пространства имен System.Reflection и PW_CommonType. Вспомните, что цель создания данного приложения состоит в том, чтобы увидеть, как использовать позднее связывание и рефлексию для проверки отдельных двоичных файлов, создаваемых другими производителям, на предмет их способности выступать в роли подключаемых оснасток.

Вдаваться в детали разработки приложения Windows Forms не будем, а просто разместим в окне конструктора форм компонент MenuStrip и определим для него одно главное меню Файл с единственным подменю Подключить модуль. Добавим в главное окно элемент ListBox, чтобы отображать в нем имена загружаемых пользователем оснасток. На рисунке показано, как должен выглядеть в конечном итоге графический пользовательский интерфейс разрабатываемого приложения:

Тестовое приложение
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using PW_CommonType;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                if (ofd.FileName.Contains("PW_CommonType"))
                    MessageBox.Show("PW_CommonType не содержит оснасток");
            }
        }

        public bool LoadModule(string Path)
        {
            bool found = false;
            Assembly asm = null;
            try
            {
                asm = Assembly.LoadFrom(Path);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                return found;
            }

            // Используем LINQ-запрос
            var ClassTypes = from t in asm.GetTypes() where t.IsClass && 
                                                            (t.GetInterface("IApplicationFunc") != null)
                                                            select t;
            foreach (Type t in ClassTypes)
            {
                found = true;
                IApplicationFunc itApp = (IApplicationFunc)asm.CreateInstance(t.FullName, true);
                itApp.Go();
                listView1.Items.Add(t.FullName);
            }
            return found;
        }

    }
}
Лучший чат для C# программистов