Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4454.html
 

Реализация перечисления 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. Структура файла достаточно проста, и для нее эти методы позволяют возвращать следующее:
  1. getValue возвращает текстовое значение тега.
  2. getAttribute получается атрибут тега по имени
  3. 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;
В действительности, наверное, некоторые скажут, что ерундистику какую то написал :), возможно так и есть :)
Метки:  IEnumerable 

Комментарии

Юрий
20.01.2011 в 15:38
> В действительности, наверное, некоторые скажут, что ерундистику какую то написал :), возможно так и есть :)
:-)))

Красиво написано в плане применения последних паскалевских нововведений.
Единственное неясно из статьи как это может быть использовано, какие практические плюсы даёт эта конструкция ( чем она отличается от обычного масива соединений, я так понял это пул коннектов)?
ter
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
Просто наткнулся на инфу, для C#. Функциональность LINQ to Objects обеспечивается интерфесом IEnumerable...
ter
01.02.2011 в 22:57
http://msdn.microsoft.com/en-us/library/h1x9x1b1.aspx интерфейс IEnumerable в .NET
если вы в Delphi импортируете библиотеку mscorlib.dll
то получите этот интерфейс его идентификатором 496B0ABE-CDEE-11d3-88E8-00902754C43A
отличие будет в моделях вызовов. т.е по умолчанию в delphi используется register то в импортируемом варианте будет stdcall.

Поэтому импортированный интерфейс можно добавить в свой проект, переопределив модели вызовов у классов реализующих вероятно получим работоспособный вариант для скрещивания с C#. К сожалению не пробовал пока что такого делать.

возможно в делфи этот GUID куда то для него запрятан, или фик знает. В общем итоге, в чистом виде использовать с С# его вряд ли получится, тут я согласен полностью.
Do you mind if I quote a few of your posts as long as I provide credit and sources back to
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!
en
31.01.2018 в 12:09
Driven that that sublingual reviews for men not not not.
20
05.03.2018 в 11:10
generic , online pharmacy for cialis .
James
12.04.2018 в 20:01
That alone wwas an egregious oversight on thheir own part, since
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно