Форум программистов, компьютерный форум, киберфорум
Наши страницы
Rius
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 2.

C# .Net + WindowsForms + плагины

Запись от Rius размещена 05.09.2015 в 17:11
Обновил(-а) Rius 03.08.2016 в 13:38

1. Обычный способ сбора плагинов.
  • Рекурсивно в каталоге с приложением ищутся все dll файлы сборок.
  • Найденные по очереди перебираются и загружаются в память с помощью Assembly.LoadFile().
  • Из сборки достаются все существующие в ней типы. Опять же, по одному перебираются и проверяются на наличие интерфейса плагина.
  • Если таковой у типа имеется, тип считается плагином и сохраняется для последующего использования.

Пример кода на GitHub

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private List<IPlugin> ScanPlugins()
{
    List<IPlugin> result = new List<IPlugin>();
 
    string[] files = this.GetLibraries();
 
    var watch = System.Diagnostics.Stopwatch.StartNew();
 
    foreach (var filename in files)
    {
        try
        {
            Assembly assembly = Assembly.LoadFile(filename);
            Type[] types = assembly.GetTypes();
 
            foreach (var type in types)
            {
                Type t = type.GetInterface(typeof(IPlugin).FullName);
 
                if (t != null && !type.IsAbstract)
                {
                    IPlugin pluginInfo = (IPlugin)Activator.CreateInstance(type);
                    result.Add(pluginInfo);
                }
            }
        }
        catch (Exception exc)
        {
            System.Diagnostics.Debug.WriteLine(exc.Message);
        }
    }
 
    watch.Stop();
    var elapsedMs = watch.ElapsedMilliseconds;
 
    return result;
}
Просто. Удобно. Распространено. Долго.

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

Пример кода на GitHub

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace MyApp.Core.Classes
{
    [AttributeUsage(AttributeTargets.Assembly)]
    public class AssemblyPluginTypeAttribute : Attribute
    {
        public Type PluginType { get; private set; }
 
        public AssemblyPluginTypeAttribute(Type value) { this.PluginType = value; }
    }
}
В файлах AssemblyInfo.cs сборок плагинов прописываются эти атрибуты, с указанием типа плагина.
C#
1
[assembly: MyApp.Core.Classes.AssemblyPluginType(typeof(MyApp.About.Classes.Plugin))]
Теперь поиск типов плагинов немного иной:
  • Рекурсивно в каталоге с приложением ищутся все dll файлы сборок.
  • Найденные по очереди перебираются и загружаются в память с помощью Assembly.LoadFile().
  • Из сборки достаются её указанные атрибуты. Из атрибутов получаются типы, реализующие интерфейс плагина.
  • Тип плагина сохраняется для последующего использования.
Участок кода проверки всех типов в сборке теперь отсутствует. Атрибуты запрашиваются только у самой сборки.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private List<IPlugin> ScanPlugins()
{
    List<IPlugin> result = new List<IPlugin>();
 
    string[] files = this.GetLibraries();
 
    var watch = System.Diagnostics.Stopwatch.StartNew();
 
    foreach (var filename in files)
    {
        try
        {
            Assembly assembly = Assembly.LoadFile(filename);
            object[] attributesPlugin = assembly.GetCustomAttributes(typeof(AssemblyPluginTypeAttribute), false);
 
            foreach (var attributePlugin in attributesPlugin)
            {
                Type pluginType = (attributePlugin as AssemblyPluginTypeAttribute).PluginType;
                Type t = pluginType.GetInterface(typeof(IPlugin).FullName);
 
                if (t != null && !pluginType.IsAbstract)
                {
                    IPlugin pluginInfo = (IPlugin)Activator.CreateInstance(pluginType);
                    result.Add(pluginInfo);
                }
            }
        }
        catch (Exception exc)
        {
            System.Diagnostics.Debug.WriteLine(exc.Message);
        }
    }
 
    watch.Stop();
    var elapsedMs = watch.ElapsedMilliseconds;
 
    return result;
}

Время выполнения проверки (в одной моей небольшой программке) по первому способу составляет в среднем 390 мс. По второму - 90 мс.
Не так важны сами числа, как их соотношение. Без перебора всех типов в сборке время проверки сокращается. Чем больше типов содержится в сборке, тем более это должно быть заметно.

Можно было бы в первом способе попробовать применить assembly.GetType(string typeName), но это чревато жёсткими требования к именам классов плагина в сборках и ошибками при рефакторинге (правильность строк не отслеживается компилятором).


3. Немного параллельности.
Будет ли выгода, зависит от длительности инициализации плагинов. Если оно и так мало, то снижение времени инициализации всего списка плагинов с 6 до 2 мс будет совершенно незаметно на фоне 100-200 мс, потраченных на загрузку файлов сборок в память.
При некоторых условиях загрузка сборок в память может и замедлиться.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        private List<IPlugin> ScanPlugins(IApplication app)
        {
            List<IPlugin> result = new List<IPlugin>();
 
            var files = this.GetLibraries();
 
            try
            {
                var plugins = from filename in files.AsParallel()
                              let assembly = Assembly.LoadFile(filename)
                              from attributePlugin in assembly.GetCustomAttributes(typeof(AssemblyPluginTypeAttribute), false).AsParallel()
                              let pluginType = (attributePlugin as AssemblyPluginTypeAttribute).PluginType
                              let interfaceType = pluginType.GetInterface(typeof(IPlugin).FullName)
                              where interfaceType != null && !pluginType.IsAbstract
                              let pluginInfo = (IPlugin)Activator.CreateInstance(pluginType) // Создание экземпляра
                              select pluginInfo;
 
                result = plugins.ToList();
 
                Parallel.ForEach(result, (plugin) =>
                    {
                        plugin.Initialize(app); // Дополнительная инициализация каждого экземпляра
                    });
            }
            catch (Exception exc)
            {
                System.Diagnostics.Debug.WriteLine(exc.Message);
            }
 
            return result;
        }
Размещено в C# .Net
Просмотров 737 Комментарии 1
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Аватар для Yury Komar
    хорошая статейка. спасибо.
    Запись от Yury Komar размещена 28.07.2016 в 18:47 Yury Komar вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru