Реализация перечисления IEnumerable
Опубликовано 19.01.2011 г. 19:10
Решил первый раз написать код с использованием IEnumerable, вернее не просто с использованием получая на него интерфейс, а реализовать класс его поддерживающий.
Задачка выглядит примерно следующим образом, есть, скажем, класс TConfigurationManager, который может предоставлять информацию о некоторых настройках. Сама такая "единица" доступа к настройке (пока что для чтения) описывается следующим интерфейсом:
IConfigOption = interface ['{B31704BB-6214-4602-B44E-2759235DDE27}'] function getAttribute(const attr : WideString): WideString; safecall; function getParameter(const name : WideString): WideString; safecall; function getValue() : WideString; safecall; end;Менеджер работает с файлом формата XML. Структура файла достаточно проста, и для нее эти методы позволяют возвращать следующее:
- getValue возвращает текстовое значение тега.
- getAttribute получается атрибут тега по имени
- getParameter получает текстовое значение дочернего узла. От getValue здесь есть некоторое отличие.
<?xml version="1.0" encoding="windows-1251"?> <Сonfig> <DatabaseConnections> <Connect> <server>dbserver</server> <database>test1</database> </Connect> <Connect> <server>dbserver</server> <database>test2</database> </Connect> <Connect> <server>dbserver</server> <database>test3</database> </Connect> </DatabaseConnections> <AttributesTest> <x attrName="Attrvalue"/> </AttribuesTest> <Files> <LogFile>my.log</LogFile> <SomeFile>somfile.txt</SomeFile> </Files> </Config>Мы видим, что узел Files хранит названия различных файлов, например лог файла. Узел x имеет тестовый атрибут attrName; Такие значения мы можем получить используя, например, такие вызовы:
logFileName := configManager.getValue('Files/LogFile'); attrValue := configManager.getAttribute('AttributesTest/x', 'attrName');Теперь обратим внимание на первый узел - DatabaseConnections, он содержит перечисление узлов Connect, в каждом из которых содержится информация о подключении: имя сервера, имя БД. Теперь, получив интерфейс IConfigOption, скажем для второго узла Connect, используя метод getParameter('database') мы можем получить соответствующее значение. В этом и есть разница между getValue & getParameter. Суть использования подобного интерфейса в том, чтобы скрыть все возможности управления узлами XML, поэтому интерфейс IXMLNode не предоставляется в пользование. Тем не менее, класс TConfigurationManager основан на использовании IXMLDocument. Вот здесь в общем то возникает вопрос об использовании IEnumerable. Допустим, менеджер подключений к базе данных запрашивает список подключений, и данные ему требуется предоставить именно в виде списка, но не обычного списка на подобии TList, а с использованием интерфейсов, чтобы реализовать взаимодействие с различными модулями, реализованными в разных библиотеках, и, возможно, с использованием различных языков. Собственно IEnumerable в паре с IEnumerator предоставляют нам простейший механизм перечисления элементов коллекции. Метод IEnumerable.getEnumerator() возвращает объект IEnumerator, который в свою очередь осуществляет итерации по набору. В основе лежат три метода: getCurrent(), MoveNext(), Reset(). Смысл методов очевиден и соответствует названию, передвижения по коллекции однонаправленны, что в общем то близко к представлению односвязного списка. Очевидной реализацией перебора элементов коллекции может быть такая:
connectCollection := configManager.getOptionEnumeration('DatabaseConnections'); connectEnum := connectCollection.GetEnumerator; while (connectEnum.MoveNext) do begin connectOption := connectEnum.Current; .... end;Но реализация интерфейса IEnumerable предоставляет нам возможность воспользоваться конструкцией for .. in, например:
for connectOption in connectCollection do begin .... end;Теперь рассмотрим непосредственно реализацию. Начнем с самого менеджера конфигурации, который имеет два, интересующих нас метода: получение самого объекта IConfigOption либо нашей коллекции IOptionEnumerable, в основе лежит навигация под XML документу с использованием интерфейса IXMLDocument (частичный код):
TConfigurationManager = class(TSingletonImplementation, IConfigurationManager) function getOption(const path : WideString): IConfigOption; safecall; function getOptionEnumeration(const path : WideString) : IEnumerable; safecall; end; .... function TConfigurationManager.getOption(const path: WideString): IConfigOption; var node : IXMLNode; begin result := nil; node := getNode(path); if node <> nil then result := TConfigOption.Create(node); end; function TConfigurationManager.getOptionEnumeration(const path: WideString): IEnumerable; var node : IXMLNode; begin result := nil; node := getNode(path); if node <> nil then result := TOptionEnumerable.Create(node); end;Собственно по методы по заданному пути (например, Files/LogFile) находят интересующий узел, и на его основе создают объекты, описывающие либо коллекцию, либо единичный объект. Описание класса TConfigOption приведено ниже, его же реализация не представляет особого интереса, по скольку методы только лишь получают данные из IXMLNode-узла:
TConfigOption = class (TInterfacedObject, IConfigOption) strict private FNode : IXMLNode; public constructor Create(var aNode : IXMLNode); destructor Destroy(); override; function getAttribute(const attr : WideString): WideString; safecall; function getParameter(const name : WideString): WideString; safecall; function getValue() : WideString; safecall; end;Класс TOptionEnumerator реализует интерфейс IEnumerator, причем не простой IEnumerator, возвращающий базовые объекты TObject, а IEnumerator<T>, в нашем случае IEnumerator<IConfigOption>. Описание класса имеет следующий вид:
TOptionEnumerator = class(TInterfacedObject, IEnumerator<IConfigOption>) strict private FIndex : integer; FNode : IXMLNode; function getCurrentOption() : IConfigOption; public constructor Create(var aNode : IXMLNode); destructor Destroy(); override; function getCurrent : TObject; function IEnumerator<IConfigOption>.GetCurrent = getCurrentOption; function MoveNext : boolean; procedure Reset; end;Стоит обратить внимание на присутствие двух методов GetCurrent. Действительно, ведь у нас есть как IEnumerator, так и IEnumerator<IConfigOption>, каждый из которых возвращает свой тип данных. Что касается реализации, то выглядит она следующим образом. При этом стоит отметить, что метод getCurrent, возвращающий тип TObject, всегда будет возвращать пустую ссылку.
{$REGION '----------------------------- TOptionEnumerator ----------------------------------' } constructor TOptionEnumerator.Create(var aNode: IXMLNode); begin inherited Create(); FIndex := -1; FNode := aNode; end; destructor TOptionEnumerator.Destroy; begin FNode := nil; inherited; end; function TOptionEnumerator.getCurrent: TObject; begin result := nil; end; function TOptionEnumerator.getCurrentOption: IConfigOption; var targetNode : IXMLNode; begin targetNode := FNode.ChildNodes.Get(FIndex); result := TConfigOption.Create(targetNode); end; function TOptionEnumerator.MoveNext: boolean; begin result := FIndex < FNode.ChildNodes.Count - 1; if result then inc(FIndex); end; procedure TOptionEnumerator.Reset; begin FIndex := -1; end;Что касается объекта, реализующего интерфейс IEnumerable то он имеет следующую реализацию:
TOptionEnumerable = class(TInterfacedObject, IEnumerable<IConfigOption>) strict private FNode : IXMLNode; function getOptionEnumerator() : IEnumerator<IConfigOption>; public constructor Create(var aNode : IXMLNode); destructor Destroy(); override; function GetEnumerator: IEnumerator; function IEnumerable<IConfigOption>.GetEnumerator = getOptionEnumerator; end; ... constructor TOptionEnumerable.Create(var aNode: IXMLNode); begin inherited Create(); FNode := aNode; end; destructor TOptionEnumerable.Destroy; begin FNode := nil; inherited; end; function TOptionEnumerable.GetEnumerator: IEnumerator; begin result := nil; end; function TOptionEnumerable.getOptionEnumerator: IEnumerator; begin result := TOptionEnumerator.Create(FNode) end;В действительности, наверное, некоторые скажут, что ерундистику какую то написал :), возможно так и есть :)
20.01.2011 в 15:38
:-)))
Красиво написано в плане применения последних паскалевских нововведений.
Единственное неясно из статьи как это может быть использовано, какие практические плюсы даёт эта конструкция ( чем она отличается от обычного масива соединений, я так понял это пул коннектов)?
20.01.2011 в 16:35
коннекты к БД в данном случае просто как пример были написаны.
По факту, описывается коллекция каких то объектов, которые поддерживают какие либо интерфейсы.
Обычный список чего либо (TList) мы не сможет отдать в dll написанную скажем на MS C++. А с использованием интерфейсов сможем. а интерфейс IEnumerable поддерживается наверное всеми языками, ибо базовый.
31.01.2011 в 20:24
>Обычный список чего либо (TList) мы не сможет отдать в dll написанную скажем на MS C++. А с использованием интерфейсов сможем. а интерфейс IEnumerable поддерживается наверное всеми языками, ибо базовый.
Я конечно могу ошибаться, ну я больше чем уверен, что IEnumerable Delphi не подойдёт на MS C++ или С#. Я правда не нашёл ничего по этому поводу... Если вдруг есть какая нибудь ссылка, поделитесь, плиз.
31.01.2011 в 21:51
01.02.2011 в 22:57
если вы в Delphi импортируете библиотеку mscorlib.dll
то получите этот интерфейс его идентификатором 496B0ABE-CDEE-11d3-88E8-00902754C43A
отличие будет в моделях вызовов. т.е по умолчанию в delphi используется register то в импортируемом варианте будет stdcall.
Поэтому импортированный интерфейс можно добавить в свой проект, переопределив модели вызовов у классов реализующих вероятно получим работоспособный вариант для скрещивания с C#. К сожалению не пробовал пока что такого делать.
возможно в делфи этот GUID куда то для него запрятан, или фик знает. В общем итоге, в чистом виде использовать с С# его вряд ли получится, тут я согласен полностью.
29.08.2017 в 11:42
your site? My website is in the exact same niche as yours and my users would really benefit from
some of the information you provide here. Please let me know if this okay
with you. Thank you!
31.01.2018 в 12:09
05.03.2018 в 11:10
12.04.2018 в 20:01