Отражение чаще всего используется для обхода статической системы типов, однако также имеет несколько интересных случаев использования:
Давайте напишем ORM!
Если вы знакомы с NHibernate или большинством других ORM, вы пишете классы, которые сопоставляются с таблицами в вашей базе данных, что-то вроде этого:
// used to hook into the ORMs innards
public class ActiveRecordBase
{
public void Save();
}
public class User : ActiveRecordBase
{
public int ID { get; set; }
public string UserName { get; set; }
// ...
}
Как вы думаете, метод Save()
написан? Ну, в большинстве ORM метод Save не знает, какие поля находятся в производных классах, но он может получить к ним доступ, используя отражение.
Его полностью может иметь такую же функциональность безопасным для типа способом, просто требуя от пользователя переопределить метод для копирования полей в объект datarow, но это приведет к большому количеству шаблонного кода и раздувать.
Столбики!
Rhino Mocks - это насмешливый каркас. Вы передаете тип интерфейса в метод, и за кулисами фреймворк динамически создает и создает экземпляр фиктивного объекта, реализующего интерфейс.
Конечно, программист мог бы написать шаблонный код для фиктивного объекта вручную, но зачем ей это, если фреймворк сделает это для нее?
Metadata!
Мы можем декорировать методы атрибутами (метаданными), которые могут служить различным целям:
[FilePermission(Context.AllAccess)] // writes things to a file
[Logging(LogMethod.None)] // logger doesn't log this method
[MethodAccessSecurity(Role="Admin")] // user must be in "Admin" group to invoke method
[Validation(ValidationType.NotNull, "reportName")] // throws exception if reportName is null
public void RunDailyReports(string reportName) { ... }
Вам нужно подумать над методом, чтобы проверить атрибуты. Большинство AOP-структур для .NET используют атрибуты для внедрения политики.
Конечно, вы можете написать тот же самый код, но этот стиль более декларативный.
Давайте создадим структуру зависимостей!
Многие контейнеры IoC требуют некоторой степени отражения для правильной работы. Например:
public class FileValidator
{
public FileValidator(ILogger logger) { ... }
}
// client code
var validator = IoC.Resolve<FileValidator>();
Наш контейнер IoC создаст экземпляр валидатора файла и передаст соответствующую реализацию ILogger в конструктор. Какая реализация? Это зависит от того, как это реализовано.
Допустим, я дал название сборки и класс в файле конфигурации. Язык должен прочитать имя класса в виде строки и использовать отражение для его создания.
Если мы не знаем реализацию во время компиляции, не существует безопасного типа для создания экземпляра класса на основе его имени.
Позднее связывание / утка
Существует множество причин, по которым вы хотите прочитать свойства объекта во время выполнения. Я бы выбрал ведение журнала в качестве простейшего варианта использования - допустим, вы писали регистратор, который принимает любой объект и выплевывает все его свойства в файл.
public static void Log(string msg, object state) { ... }
Вы можете переопределить метод Log для всех возможных статических типов, или вы можете просто использовать отражение для чтения свойств.
Некоторые языки, такие как OCaml и Scala, поддерживают статически проверенную типизацию утки (называемую структурная типизация ), но иногда вы просто не обладаете знанием интерфейса объектов во время компиляции.
Или, как знают Java-программисты, иногда система типов может найти свое решение и потребовать от вас написать все виды шаблонного кода. Есть известная статья, в которой описывается, сколько шаблонов проектирования упрощено с помощью динамической типизации .
Время от времени обход системы типов позволяет значительно реорганизовать ваш код по сравнению со статическими типами, что приводит к немного более чистому коду (желательно скрытому за дружественным для программиста API :)). Многие современные статические языки принимают золотое правило «статическая типизация, где это возможно, динамическая типизация, где это необходимо», позволяя пользователям переключаться между статическим и динамическим кодом.