15

I'm using Lidgren and for every new type of message I make, I end up writing the same kind of code. I'm creating an instance of NetOutgoingMessage, running various assignment calls on it, then sending it when I'm done. Creation and send are the same, so I want to write a wrapper to do this for me, but it's a sealed class and it's not IDisposable. What I'm doing is something like:

NetOutgoingMessage om = server.CreateMessage();

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

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

And I want to do something like:

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

Obviously I can't do using so is there another common way if implementing functionality like this? I'm not looking for an incredibly complicated solution, as this is just about maintainability for me, so I don't have a problem repeating my code for every message. But I am curious if there's a fun C# trick I don't know about for this.

Dmitry
  • 13,797
  • 6
  • 32
  • 48
Dan B
  • 357
  • 1
  • 2
  • 15
  • 5
    you may not be able to inherit the class, but you can do a wrapper over it, whose instance creates instance of the desired class, then your wrapper can implement IDisposable calling the real class' method upon dispose – Royal Bg Sep 05 '15 at 22:18
  • But even though you can, don't do it like @RoyalBg suggests. Don't use `using` here. You would *not* want `server.SendMessage` to be called if any of the `om.Write` calls results in an exception. The point of `using` is that `Dispose()` *always* gets called, even if any part of code in the block failed. (Which I now see was already pointed out in comments on Cyral's answer too.) –  Sep 06 '15 at 14:27

2 Answers2

21

Yes, you can easily enforce this sort of temporal relationship between some calls by receiving a delegate that contains the custom actions and invoking that delegate exactly when you want.

Here's an example:

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

Call it like this:

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

Essentially, this is a form of inversion of control: the practice by which a general piece of code calls into an externally provided, specialized piece of code, at a specified point.

Typically, this is achieved using (often abstract) classes or interfaces - and making virtual calls through them. Delegates and lambdas simply allow you to express the same thing more concisely, while the compiler does the boring work (like declaring the new class, capturing required variables) behind the scenes.

Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • I was considering delegates, but wasn't sure the best way to go about doing it cleanly. This tidies it up quite nicely. I like this a lot. I'm not sure if I would rather go with the delegate or the interface proposed by Cyral, so I'll choose an "answered" when I figure out which one I'm going to use. Thank you, Theodoros! – Dan B Sep 05 '15 at 22:36
  • The problem with that is that you can't break out of that block using control flow. For example `return` has it's meaning changed. That said it'S clearly a valid solution in general. – usr Sep 06 '15 at 12:02
7

Create a wrapper around the NetOutgoingMessage class that implements 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);
    }
}

You can then use it through using:

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

Another option is to create an interface IMessage that has method definitions for Encode and Decode, and allow other classes to implement them. Then create a Send method that will write the message type and run the Encode method. This is what I do with my own applications and it works great.

Cyral
  • 13,999
  • 6
  • 50
  • 90
  • Aha, msg.Message.Write was part of what was missing for me, when I had a similar idea but didn't want to reimplement everything. (Much like RoyalBg above.) Good answer, thank you! – Dan B Sep 05 '15 at 22:34
  • 2
    Be careful when calling `SendMessage` inside `Dispose`: this means it'll be called even if the main body of code causes an exception. That isn't the behaviour I'd want/expect to happen. – LukeH Sep 05 '15 at 22:40
  • On top of @LukeH's great point, I'd really recommend against wrapping this in an `IDisposable`. The purpose of that interface is for resource management, and when you do something that doesn't fall into that bucket, you're just going to confuse people who make incorrect assumptions on that basis. The minor convenience it presents is not worth all the confusion that will arise when somebody sees that it implements `IDisposable` and automatically puts it in a `using` statement without understanding what that means. – Chris Hayes Sep 06 '15 at 04:41
  • 1
    At least implement `IDisposable` explicitly using `public void IDisposable.Dispose(){}`, that way it isn't as obviously present to others. But I agree with the others, you'll probably end up confusing others immensely using this construction. Plus you'd need to be careful as the Garbage Collector will suddenly start sending messages you *did not* actually want to send. – jessehouwing Sep 06 '15 at 11:08