Итерация по структуре 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[]
}
3 ответов
ближе всего к тому, что вы хотите, вероятно,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;
}
использовать 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)
, который также возвращает методов.
есть несколько способов сделать итерацию над объектами в D.
один должен реализовать InputRange API-интерфейс. Входные диапазоны похожи на итераторы, но имеют другой API. Реализация интерфейса диапазона означает, что вы можете использовать все std.range
/std.algorithm
функции на вашем объекте, такие как 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;
если вы реализуете выходной диапазон поверх своего сокета, то это означает, что решение выходного диапазона не нужно выделять память из кучи.
Проверьте Программирование В D книги (в частности,диапазоны и Больше Диапазонов разделы) для получения информации о том, как реализовать свои собственные диапазоны.