2015-11-30 28 views
6

ho una struct che sembra qualcosa di simile:iterare su dlang struct

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

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

e vorrei scorrere su di esso, in un'altra classe con qualcosa di simile:

struct Socket { 
    ... 

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

    ... 
} 

È questo possibile? Mi piacerebbe utilizzare qualcosa di analogo a Python __iter__ nel MultipartMessage che può restituire i campi in un ordine specifico, e idealmente anche eseguire qualche codice aggiuntivo, come header.serialize().

Idealmente vorrei aggiungere una funzione per MultipartMessage che sarebbe simile a questa (pseudocodice):

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

Come indicato nella documentazione (http://dlang.org/spec/statement.html#ForeachStatement) ci sono molti modi per trattare con dichiarazioni foreach. Il più semplice è forse il range di input, ma senza una chiara visione di ciò che vuoi che ogni iterazione restituisca è difficile dire quale sia la migliore per il tuo caso. Potresti essere più preciso? – cym13

risposta

2

La cosa più vicina a ciò che si desidera è probabilmente opApply.

Vedi http://dlang.org/spec/statement.html, Sezione Foreach over Structs and Classes wit opApply

questo funzionerà:

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

Uso tupleof:

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

Questo chiamerà sendPart con mime, data e header (i campi della struct, nell'ordine in cui sono stati dichiarati). Puoi filtrare i campi controllandone il tipo con ad es. static if (!is(typeof(part) == Header)).

per ottenere il nome del campo, è possibile utilizzare __traits(identifier):

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

(__traits(identifier, part) sarebbero tornati part.)

C'è anche __traits(allMembers), che restituisce anche i metodi.

+0

'tupleof' non è un buon analogo a' __iter__' di Python. –

+0

Forse ho frainteso la domanda, ma D's '__iter__' è' opApply'. –

+0

cosa intendi per D's '__iter__' è' opApply'? Come potrebbe essere usato per iterare su una struttura? –

2

Ci sono diversi modi per fare iterare su oggetti in D.


Uno è quello di attuare il InputRange API. Gli intervalli di input sono simili agli iteratori, ma hanno un'API diversa. Implementare un'interfaccia di intervallo significa che è possibile utilizzare tutte le funzioni std.range/std.algorithm nell'oggetto, ad esempio map, array, joiner e così via.

D non ha una funzione __iter__ per ottenere un iteratore da raccolte arbitrarie, quindi sarà necessario implementare una funzione che restituisce un intervallo di input.

import std.range; 

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

Ciò restituirà un intervallo ubyte ingresso, costituito dal byte mime, seguiti dai byte data, poi in header.serialize.


È inoltre possibile implementare il metodo opApply nella struct. opApply funziona solo con foreach, quindi non è possibile utilizzare i metodi di intervallo con esso, ma consente di eseguire operazioni come eseguire il corpo del ciclo in thread separati.

L'essenza di opApply è che D passa il corpo del ciclo a opApply come una funzione; ovvero, foreach(x; myObj) { body } viene trasformato in myObj.opApply((x) { body }).

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

Tuttavia, invece di una di queste opzioni, vi consiglio di implementare una funzione sul vostro oggetto che prende una gamma uscita e scrive i dati su di esso.

Un intervallo di output è un oggetto che accetta altri oggetti e fa loro qualcosa. In questo caso, l'intervallo di output deve accettare ubyte s, rendendolo simile a un flusso di output.

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 
} 

Esempio di utilizzo, che memorizza l'uscita in un Appender, che può essere convertito in un array:

import std.array; 

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

Se si implementa un campo di uscita sulla parte superiore della presa, allora ciò significa che la La soluzione per l'intervallo di produzione non deve allocare alcuna memoria dall'heap.


Scopri i Programming in D libro (in particolare, le Ranges e più intervalli sezioni) per informazioni su come implementare le proprie gamme.