类似于“using”的东西,可以在创建对象并调用方法后让我在中间执行自己想要的操作

15

我正在使用Lidgren, 每次我创建新的消息类型时,我都需要编写相同类型的代码。我会创建一个NetOutgoingMessage实例,对其进行各种分配调用,然后在完成后发送它。创建和发送是一样的,所以我想编写一个包装器来为我完成这个过程,但这是一个sealed类,并且不是IDisposable。我正在做的类似于:

NetOutgoingMessage om = server.CreateMessage();

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

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

而我希望做类似于以下的事情:

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

显然,我不能使用using,那么是否有另一种常见的方式来实现这样的功能?我不想要一个非常复杂的解决方案,因为对我来说这只是关于可维护性的问题,所以我不介意为每个消息重复我的代码。但是我很好奇是否有我不知道的有趣的C#技巧可以实现这个。


5
虽然你可能无法继承这个类,但你可以创建一个包装器(wrapper)来包装它,它的实例可以创建所需类的实例。然后,你的包装器可以实现IDisposable接口,在dispose时调用真实类的方法。 - Royal Bg
但是即使你可以这样做,也不要像@RoyalBg建议的那样。不要在这里使用using。如果任何一个om.Write调用导致异常,您将希望调用server.SendMessageusing的重点是Dispose() 始终被调用,即使块中的代码的任何部分失败。(我现在看到这也已经在Cyral的答案评论中指出了。) - user743382
2个回答

21

是的,你可以通过接收包含自定义操作的委托并在需要的时候精确调用该委托来轻松地强制执行某些调用之间的时间关系。

以下是一个例子:

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

像这样调用:

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

本质上,这是一种控制反转的形式:控制反转(Inversion of Control)的实践方式,在指定的点调用一个外部提供的专业代码。

通常,这是通过使用(通常是抽象的)类或接口来实现,并通过它们进行虚拟调用来实现的。委托和Lambda只是让您更简洁地表达相同的内容,而编译器在幕后完成了繁琐的工作(如声明新类,捕获所需变量)。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dan B
问题在于你无法使用控制流程跳出该块。例如,return的含义已经改变。尽管如此,总的来说这仍然是一个有效的解决方案。 - usr

7

创建一个包装器,围绕着实现IDisposable的NetOutgoingMessage类。

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);
    }
}

然后您可以通过 using 使用它:

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

另一个选项是创建一个接口IMessage,其中包含Encode和Decode方法的定义,并允许其他类实现它们。然后创建一个Send方法,该方法将写入消息类型并运行Encode方法。这就是我在自己的应用程序中所做的,效果很好。

啊哈,msg.Message.Write是我缺失的部分,当我有类似的想法但不想重新实现一切时。 (就像上面的RoyalBg一样。)好答案,谢谢! - Dan B
2
在调用 Dispose 内部的 SendMessage 时要小心:这意味着即使代码主体引发异常,它也会被调用。这不是我想要或期望发生的行为。 - LukeH
除了@LukeH的好观点之外,我真的不建议将其包装在“IDisposable”中。该接口的目的是用于资源管理,当您执行不属于该范畴的操作时,您只会让那些基于此做出错误假设的人感到困惑。它所提供的微小便利性并不值得所有人看到它实现了“IDisposable”并自动将其放入“using”语句中而不理解其含义时所产生的所有混乱。 - Chris Hayes
1
至少要使用 public void IDisposable.Dispose(){} 显式地实现 IDisposable,这样它对其他人来说就不是那么明显了。但我同意其他人的观点,你很可能会让其他人非常困惑。此外,你需要小心,因为垃圾回收器会突然发送一些你实际上并不想发送的消息。 - jessehouwing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接