Предыдущая лекция | Следующая лекция | |
---|---|---|
Шаблоны проектирования. | Содержание | Обзор типов оконных приложений в C#. Знакомство со структорой проекта. Компоновка. |
DLL (англ. Dynamic Link Library — «библиотека динамической компоновки», «динамически подключаемая библиотека») в операционных системах Microsoft Windows и IBM OS/2 — динамическая библиотека, позволяющая многократное использование различными приложениями.
Во-первых, надо отметить, что "классические" DLL содержат функции. А .NET, как мы уже знаем, оперирует класами и пространствами имён. Поэтому DLL, создаваемые в .NET не совместимы с обычными. (Но если очень надо, то как-то это реализуется. Технология Windows Forms использует Win32 API)
Во-вторых, в рамках нашего курса вообще нет особой необходимости в DLL (просто не будет достаточно сложных проектов).
Но, на демо-экзамене и чемпионате в модуле тестирование может встретится задание написать библиотеку классов и unit-тесты для неё. Поэтому надо знать что это такое и уметь реализовать.
Нередко различные классы и структуры оформляются в виде отдельных библиотек, которые компилируются в файлы dll и затем могут подключать в другие проекты. Благодаря этому мы можем определить один и тот же функционал в виде библиотеки классов и подключать в различные проекты или передавать на использование другим разработчикам.
Возьмем имеющийся проект приложения .NET, например, созданный в прошлых темах.
Запомним какую платформу (.NET Core или .NET Framefork) и версию .NET используем.
Например, в моем случае .NET Core версии net8.0
В структуре проекта нажмем правой кнопкой на название решения и далее в появившемся контекстном меню выберем Add -> New Project...
Далее в списке шаблонов найдем пункт Class Library. Будьте внимательны, в списке шаблонов несколько вариантов "Библиотек классов" под разные платформы. Создаваемая библиотека классов должна соответсвовать платформе и версии проекта, к которому её планируется подключать:
После создания проекта он появится в решении, в моем случае с названием ClassLibrary1
:
По умолчанию новый проект имеет один пустой класс Class1 в файле Class1.cs
. Мы можем этот файл удалить или переименовать, как нам больше нравится.
namespace ClassLibrary;
public class Class1
{
}
Например, переименуем файл Class1.cs
в Person.cs
, а класс Class1 в Person. Определим в классе Person простейший код:
public class Person
{
public string name;
public int age;
}
ОБРАТИТЕ ВНИМАНИЕ!!!
IDE может создать класс без модификатора public - установите его, иначе не сможете использовать этот класс в других проектах.
Теперь скомпилируем библиотеку классов. Для этого нажмём правой кнопкой на проект библиотеки классов и в контекстном меню выберем пункт Build Selected Projects.
После компиляции библиотеки классов в папке проекта в каталоге bin/Debug
мы сможем найти скомпилированный файл dll (ClassLibrary.dll
).
Подключим созданную библиотеку классов в основной проект. Для этого в основном проекте нажмем правой кнопкой на Dependencies (Зависимости) и в контекстном меню выберем пункт Reference...:
Далее нам откроется окно для добавления зависимостей. Если Вы всё сделали правильно (библиотека находится в том же решении) то в этом будет пункт Projects со списком проектов в решениии, среди которых будет ваша библиотека (в моём случае <ClassLibrary1>
).Поставим отметку рядом с нашей библиотекой.
Если наша библиотека вдруг представляет файл dll, который не связан ни с каким проектом в нашем решении, то с помощью кнопки Add From мы можем найти местоположение файла dll и также его подключить.
После успешного подключения библиотеки в главном проекте изменим код, чтобы он использовал класс Person из библиотеки классов:
using ClassLibrary1;
var tom = new Person {
name = "Tom",
age = 35 };
Console.WriteLine(tom.name);
Такое подключение библиотек назвается статическим.
При создании приложения для него определяется набор сборок, которые будут использоваться. В проекте указываются ссылки на эти сборки, и когда приложение выполняется, при обращении к функционалу этих сборок они автоматически подгружаются.
Но также мы можем сами динамически подгружать другие сборки, на которые в проекте нет ссылок.
Чтобы не делать новых библиотек, мы можем удалить ссылку на уже добавленную:
Для управления сборками в пространстве имен System.Reflection имеется класс Assembly. С его помощью можно загружать сборку, исследовать ее.
Чтобы динамически загрузить сборку в приложение, надо использовать статические методы Assembly.LoadFrom() или Assembly.Load().
Метод LoadFrom() принимает в качестве параметра путь к сборке. Например, исследуем сборку на наличие в ней различных типов:
Чтобы не писать длинных относительных или, не дай бог, абсолютных путей нужно скопировать
ClassLibrary1.dll
в каталогbin/Debug/<версия .net>/
(можно прямо в IDE)
using System.Reflection;
var asm = Assembly.LoadFrom(
"ClassLibrary1.dll");
Console.WriteLine(asm.FullName);
var types = asm.GetTypes();
foreach(Type t in types)
{
Console.WriteLine(t.Name);
}
На выходе получим примерно такое:
ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Person
Process finished with exit code 0.
В данном случае для исследования указывается библиотека ClassLibrary1.dll
. Здесь использован относительный путь, так как сборка находится в одной папке с приложением.
Метод Load() действует аналогично, только в качестве его параметра передается дружественное имя сборки, которое нередко совпадает с именем приложения: Assembly asm = Assembly.Load("MyApp");
Получив все типы сборки с помощью метода GetTypes(), мы можем исследовать их или применить в своем проекте.
Класс System.Type представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью его свойств и методов можно получить эту информацию. Некоторые из его свойств и методов:
Чтобы управлять типом и получать всю информацию о нем, нам надо сперва получить данный тип. Это можно сделать тремя способами: с помощью ключевого слова typeof, с помощью метода GetType() класса Object и применяя статический метод Type.GetType().
Получение типа через typeof:
Type myType = typeof(User);
Console.WriteLine(myType.ToString());
Console.ReadLine();
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public User(string n, int a)
{
Name = n;
Age = a;
}
public void Display()
{
Console.WriteLine($"Имя: {Name} Возраст: {Age}");
}
public int Payment(int hours, int perhour)
{
return hours * perhour;
}
}
Здесь определен класс User с некоторым набором свойств и полей. И чтобы получить его тип, используется выражение Type myType = typeof(User);
Получение типа с помощью метода GetType класса Object:
User user = new User("Tom", 30);
Type myType = user.GetType();
В отличие от предыдущего примера здесь, чтобы получить тип, надо создавать экземпляр класса.
И третий способ получения типа - статический метод Type.GetType():
Type myType = Type.GetType("TestConsole.User", false, true);
Первый параметр указывает на полное имя класса с пространством имен. В данном случае класс User находится в пространстве имен TestConsole. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение false означает, что исключение не будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение true означает, что регистр игнорируется.
В данном случае класс основной программы и класс User находятся в одном проекте и компилируются в одну сборку exe. Однако может быть, что нужный нам класс находится в другой сборке dll. Для этого после полного имени класса через запятую указывается имя сборки:
Type myType = Type.GetType("TestConsole.User, MyLibrary", false, true);
Примеры из этого раздела я на
Rider
не переписывал - попробуйте сами
С помощью динамической загрузки мы можем реализовать технологию позднего связывания. Позднее связывание позволяет создавать экземпляры некоторого типа, а также использовать его во время выполнения приложения.
Использование позднего связывания менее безопасно в том плане, что при жестком кодировании всех типов (ранее связывание) на этапе компиляции мы можем отследить многие ошибки. В то же время позднее связывание позволяет создавать расширяемые приложения, когда дополнительный функционал программы неизвестен, и его могут разработать и подключить сторонние разработчики.
Ключевую роль в позднем связывании играет класс System.Activator. С помощью его статического метода Activator.CreateInstance() можно создавать экземпляры заданного типа.
Например, динамически загрузим сборку и вызовем у ней некоторый метод. Допустим, загружаемая сборка MyApp.dll
представляет следующий класс:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(GetResult(6, 100, 2));
Console.ReadLine();
}
public static double GetResult(int percent, double capital, int year)
{
for (int i = 0; i < year; i++ )
{
capital += capital /100 * percent;
}
return capital;
}
}
Итак, у нас стандартный класс Program с двумя методами. Теперь динамически подключим сборку с этой библиотекой в другой программе и вызовем ее методы.
Пусть наша основная программа будет выглядеть так:
static void Main(string[] args)
{
try
{
var asm = Assembly.LoadFrom(
"MyApp.dll");
var t = asm.GetType(
"MyApp.Program", true, true);
// создаем экземпляр класса Program
object obj = Activator.CreateInstance(t);
// получаем метод GetResult
MethodInfo method = t.GetMethod("GetResult");
// вызываем метод, передаем ему значения для параметров и получаем результат
object result = method.Invoke(obj, new object[] { 6, 100, 3 });
Console.WriteLine((result));
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
Исследуемую сборку можно получить с помощью выражения var asm = Assembly.LoadFrom("MyApp.dll")
.
Затем с помощью метода GetType получаем тип - класс Program, который находится в сборке MyApp.dll: var t = asm.GetType("MyApp.Program", true, true);
Получив тип, создаем его экземпляр: object obj = Activator.CreateInstance(t)
. Результат создания - объект класса Program представляет собой переменную obj.
И в конце остается вызвать метод. Во-первых, получаем сам метод: MethodInfo method = t.GetMethod("GetResult");
. И потом с помощью метода Invoke вызываем его: object result = method.Invoke(obj, new object[] { 6, 100, 3 })
. Здесь первый параметр представляет объект, для которого вызывается метод, а второй - набор параметров в виде массива object[].
Так как метод GetResult возвращает некоторое значение, то мы можем его получить из метода в виде объекта типа object.
Если бы метод не принимал параметров, то вместо массива объектов использовалось бы значение null: method.Invoke(obj, null)
В сборке MyApp.dll
в классе Program также есть и другой метод - метод Main. Вызовем теперь его:
Console.WriteLine("Вызов метода Main");
method = t.GetMethod("Main", BindingFlags.DeclaredOnly
| BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
method.Invoke(obj, new object[]{new string[]{}});
Так как метод Main является статическим и не публичным, то к нему используется соответствующая битовая маска BindingFlags.NonPublic | BindingFlags.Static
. И поскольку он в качестве параметра принимает массив строк, то при вызове метода передается соответствующее значение: method.Invoke(obj, new object[]{new string[]{}})
Реализуйте примеры из лекции используя классы из своей предметной области.
Предыдущая лекция | Следующая лекция | |
---|---|---|
Шаблоны проектирования. | Содержание | Обзор типов оконных приложений в C#. Знакомство со структорой проекта. Компоновка. |