2015-11-30 2 views
6

У меня есть структура, которая выглядит примерно так:перебрать dlang структуру

struct MultipartMessage { 
    ubyte[] mime, data; 
    Header header; 

    void setSender(string sender) { 
     header.sender = sender; 
    } 
    void setId(int id) { 
     header.id = id; 
    } 
} 

, и я хотел бы итерацию над ним, в другом классе с чем-то вроде этого:

struct Socket { 
    ... 

    void send(MultipartMessage msg) { 
     foreach (part; msg) { 
      sendPart(part); 
     } 
    } 

    ... 
} 

Является ли это возможное? Я хотел бы использовать что-то аналогичное Python __iter__ в MultipartMessage, которое может возвращать поля в определенном порядке и в идеале даже запускать некоторый дополнительный код, например header.serialize().

В идеале я хотел бы добавить функцию MultipartMessage, что будет выглядеть примерно так (псевдокод):

ubyte[] __iter__() { 
    yield mime; 
    yield data; 
    yield header.serialize(); //header.serialize returns a ubyte[] 
} 
+0

Как указано в документации (http://dlang.org/spec/statement.html#ForeachStatement) Есть много способов иметь дело с заявлениями foreach. Самый простой - это, возможно, диапазон ввода, но без четкого представления о том, что вы хотите, чтобы каждая итерация возвращалась, трудно сказать, какой путь является лучшим для вашего случая. Не могли бы вы уточнить? – cym13

ответ

2

Ближайшая вещь к тому, что вы хотите, вероятно, opApply.

См http://dlang.org/spec/statement.html, раздел Foreach over Structs and Classes wit opApply

Это будет работать:

int opApply(int delegate(ref ubyte[]) dg) { 
    int result = 0; 
    result = dg(mime); 
    result = dg(data); 
    ubyte[] header_bytes = header.serialize(); 
    result = dg(header_bytes); 
    return result; 
} 
8

Используйте tupleof:

foreach (ref part; msg.tupleof) 
    sendPart(part); 

Это будет вызывать sendPart с mime, data и header (поля структуры, в том порядке, в котором они были объявлены). Вы можете фильтровать поля, проверяя их тип, например. static if (!is(typeof(part) == Header)).

Чтобы получить название поля, вы можете использовать __traits(identifier):

foreach (i, ref part; msg.tupleof) 
    writeln(__traits(identifier, msg.tupleof[i])); 

(__traits(identifier, part) вернется part.)

Там также __traits(allMembers), который также возвращает методы.

+0

'tupleof' не является хорошим аналогом' __iter__' Python. –

+0

Возможно, я неправильно понял вопрос, но D 's' __iter__' является 'opApply'. –

+0

Что вы подразумеваете под D's '__iter__' is' opApply'? Как это можно использовать для перебора структуры? –

2

Есть несколько способов сделать итерацию над объектами в D.


One является реализация InputRange API. Диапазоны ввода аналогичны итераторам, но имеют разные API. Внедрение интерфейса диапазона означает, что вы можете использовать все функции на вашем объекте, например map, array, joiner и так далее.

D не имеет функции __iter__ для получения итератора из произвольных коллекций, поэтому вам нужно будет реализовать функцию, которая возвращает диапазон ввода.

import std.range; 

auto bytes() { 
    return chain(mime, data, header.serialize); 
} 

Это будет возвращать диапазон в ubyte ввода, состоящий из байтов в mime, за которым следуют байт в data, то в header.serialize.

Вы также можете реализовать метод opApply в своей структуре. opApply будет работать только с foreach, поэтому вы не можете использовать методы диапазона с ним, но он позволяет выполнять такие действия, как выполнить тело цикла в отдельных потоках.

Суть opApply заключается в том, что D передает тело петли в opApply как функцию; то есть foreach(x; myObj) { body } превращается в myObj.opApply((x) { body }).

void opApply(void delegate(ubyte[] part) loopbody) { 
    loopbody(mime); 
    loopbody(data); 
    loopbody(header.serialize()); 
} 

Однако, вместо того, чтобы ни один из этих вариантов, я рекомендую вам реализовать функцию на вашем объекте, который принимает диапазонвывода и записывает в него данные.

Выходной диапазон - это объект, который принимает другие объекты и что-то делает с ними. В этом случае выходной диапазон должен принимать ubyte s, что делает его похожим на выходной поток.

void serialize(Range)(ref Range outRange) if(isOutputRange!(Range, ubyte)) { 
    put(outRange, mime); -- `put` simply feeds data into the output range 
    put(outRange, data); 
    header.serialize(outRange); // No longer have to allocate/return a ubyte array 
} 

Пример использования, который хранит выход в Appender, которые могут быть преобразованы в массив:

import std.array; 

auto serializedDataAppender = appender!ubyte(); 
myMsg.serialize(serializedDataAppender); 
auto serializedData = serializedDataAppender.data; 

Если вы реализуете диапазон выходного сигнала на верхней части розетки, то это означает, что выходное решение не должно выделять любую память из кучи.


Отъезд Programming in D книги (в частности, Изменяется и Другие диапазоны секции) для информации о том, как реализовать свои собственные диапазоны.