2015-09-05 11 views
15

Sto usando Lidgren e per ogni nuovo tipo di messaggio che faccio, finisco per scrivere lo stesso tipo di codice. Sto creando un'istanza di NetOutgoingMessage, eseguendo varie chiamate di assegnazione su di esso, quindi inviandolo quando ho finito. La creazione e l'invio sono uguali, quindi voglio scrivere un wrapper per farlo, ma è una classe sealed e non è IDisposable. Quello che sto facendo è qualcosa di simile:Qualcosa di simile a "using" che creerà un oggetto e richiamerà un metodo al termine, ma fammi fare quello che voglio tra

NetOutgoingMessage om = server.CreateMessage(); 

om.Write(messageType); 
om.Write(data1); 
om.Write(data2); 

server.SendMessage(om, server.Connections, NetDeliveryMethod.UnreliableSequenced, 0); 

E io voglio fare qualcosa di simile:

using(AutoNetOutgoingMessage om(server, messageType, NetDeliveryMethod.UnreliableSequenced)) 
{ 
    om.Write(data1); 
    om.Write(data2); 
} 

Ovviamente non posso fare using quindi c'è un altro modo comune se implementare la funzionalità in questo modo? Non sto cercando una soluzione incredibilmente complicata, in quanto si tratta solo di manutenibilità per me, quindi non ho problemi a ripetere il mio codice per ogni messaggio. Ma sono curioso di sapere se c'è un trucco divertente del C# di cui non so nulla.

+5

potresti non essere in grado di ereditare la classe, ma puoi eseguire un wrapper su di esso, la cui istanza crea l'istanza della classe desiderata, quindi il wrapper può implementare IDisposable chiamando il metodo della classe reale su dispose –

+0

Ma anche se tu può, non farlo come suggerisce @RoyalBg. Non usare 'using' qui. Non si * vorrebbe * chiamare 'server.SendMessage' se una qualsiasi delle chiamate 'om.Write' restituisce un'eccezione. Il punto di 'using' è che' Dispose() '* sempre * viene chiamato, anche se qualsiasi parte del codice nel blocco fallisce. (Che ora vedo è già stato sottolineato nei commenti sulla risposta di Cyral.) – hvd

risposta

21

Sì, è possibile applicare facilmente questo tipo di relazione temporale tra alcune chiamate ricevendo un delegato che contiene le azioni personalizzate e invocandolo esattamente quando lo si desidera.

Ecco un esempio:

void MyMethod(Server server, Action<NetOutgoingMessage> action) 
{ 
    NetOutgoingMessage om = server.CreateMessage(); 
    action(om); 
    server.SendMessage(om, server.Connections, NetDeliveryMethod.UnreliableSequenced, 0);  
} 

chiamata in questo modo:

MyMethod(server, om => { 
    om.Write(data1); 
    om.Write(data2); 
}); 

Essenzialmente, questa è una forma di inversion of control: la pratica con cui un pezzo generale di codice chiama in un pezzo di codice specializzato fornito esternamente, in un punto specifico.

In genere, questo viene ottenuto utilizzando classi o interfacce (spesso astratte) e effettuando chiamate virtuali attraverso di esse. Delegati e lambda ti permettono semplicemente di esprimere la stessa cosa in modo più conciso, mentre il compilatore fa il lavoro noioso (come dichiarare la nuova classe, catturando le variabili richieste) dietro le quinte.

+0

Stavo considerando i delegati, ma non ero sicuro che il modo migliore per farlo in modo pulito. Questo lo riordina abbastanza bene. Mi piace molto questo. Non sono sicuro se preferirei andare con il delegato o l'interfaccia proposta da Cyral, quindi sceglierò una "risposta" quando scoprirò quale userò. Grazie, Theodoros! –

+0

Il problema è che non è possibile uscire da quel blocco utilizzando il flusso di controllo. Ad esempio, 'return' ha il significato cambiato. Detto questo, è chiaramente una soluzione valida in generale. – usr

7

Creare un wrapper attorno alla classe NetOutgoingMessage che implementa IDisposable.

public class AutoMessage : IDisposable 
{ 
    private const NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced; 

    public NetPeer Peer { get; private set; } 

    public List<NetConnection> Recipients { get; private set; } 

    public NetOutgoingMessage Message { get; private set; } 

    public AutoMessage(NetPeer peer, List<NetConnection> recipients) 
    { 
     Peer = peer; 
     Recipients = recipients; 
     Message = peer.CreateMessage(); 
    } 

    public void Dispose() 
    { 
     Peer.SendMessage(Message, Recipients, method, 0); 
    } 
} 

è possibile utilizzare tramite using:

var data = 123; 
using (var msg = new AutoMessage(Client, Client.Connections)) 
{ 
    msg.Message.Write(data); 
} 

Un'altra opzione è quella di creare un'interfaccia IMessage che ha definizioni dei metodi per Encode e Decode e di consentire altre classi per la loro attuazione. Quindi creare un metodo Send che scriverà il tipo di messaggio ed eseguirà il metodo Encode. Questo è quello che faccio con le mie applicazioni e funziona alla grande.

+0

Aha, msg.Message.Write faceva parte di ciò che mi mancava, quando avevo un'idea simile ma non volevo reimplementare tutto. (Proprio come RoyalBg sopra.) Buona risposta, grazie! –

+2

Fare attenzione quando si chiama 'SendMessage' in' Dispose': questo significa che verrà chiamato anche se il corpo principale del codice causa un'eccezione. Questo non è il comportamento che vorrei/mi aspetto di accadere. – LukeH

+0

Oltre all'ottimo aspetto di @ LukeH, consiglio vivamente di non includere questo in un 'IDisposable'. Lo scopo di questa interfaccia è la gestione delle risorse, e quando fai qualcosa che non rientra in quel secchio, confonderai le persone che fanno ipotesi errate su quella base. La minore comodità che presenta non vale tutta la confusione che si presenterà quando qualcuno vedrà che implementa 'IDisposable' e lo inserisce automaticamente in una istruzione' using' senza capire cosa significhi. –

Problemi correlati