我只是想到了一种新的设计模式。我想知道它是否存在,如果不存在,为什么不存在(或者为什么我不应该使用它)。
我正在使用OpenGL创建游戏。在OpenGL中,您经常想要“绑定”事物 - 即,使它们成为当前上下文一段时间,然后解除绑定它们。例如,您可以调用glBegin(GL_TRIANGLES)
,然后绘制一些三角形,然后调用glEnd()
。我喜欢在中间缩进所有内容,因此很清楚它的开始和结束位置,然后我的IDE喜欢取消它们,因为没有大括号。然后我想我们可以做一些聪明的事!它基本上是这样的:
using(GL.Begin(GL_BeginMode.Triangles)) {
// draw stuff
}
GL.Begin
返回一个特殊的DrawBind
对象(带有内部构造函数)并实现IDisposable
,以便它在块的末尾自动调用GL.End()
。这样一切都保持良好对齐,你不能忘记调用end()。
这个模式有名称吗?
通常当我看到using
使用时,您可以像这样使用它:
using(var x = new Whatever()) {
// do stuff with `x`
}
但是在这种情况下,我们不需要在'used'对象上调用任何方法,因此我们不需要将它分配给任何东西,除了调用相应的end函数之外它没有用处。< / p>
对于Anthony Pegram,谁想要一个我正在处理的代码的真实示例:
在重构之前:
public void Render()
{
_vao.Bind();
_ibo.Bind(BufferTarget.ElementArrayBuffer);
GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
BufferObject.Unbind(BufferTarget.ElementArrayBuffer);
VertexArrayObject.Unbind();
}
重构后:
public void Render()
{
using(_vao.Bind())
using(_ibo.Bind(BufferTarget.ElementArrayBuffer))
{
GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
}
}
请注意,_ibo.Bind
返回的对象还记住了我想解除绑定的“BufferTarget”的第二个好处。它还会引起你对GL.DrawElements
的注意,这实际上是该函数中唯一重要的语句(它做了一些明显的事情),并且隐藏了那些冗长的解绑语句。
我想一个缺点是我不能用这种方法交错Buffer Targets。我不确定我什么时候想要,但我必须保留对绑定对象的引用并手动调用Dispose,或者手动调用end函数。
如果没有人反对,我正在配音一次性上下文对象(DCO)成语。
JasonTrue提出了一个好点,即在这种情况下(OpenGL缓冲区)嵌套使用语句将无法正常工作,因为一次只能绑定一个缓冲区。但是,我们可以通过扩展“绑定对象”来使用堆栈来解决这个问题:
public class BufferContext : IDisposable
{
private readonly BufferTarget _target;
private static readonly Dictionary<BufferTarget, Stack<int>> _handles;
static BufferContext()
{
_handles = new Dictionary<BufferTarget, Stack<int>>();
}
internal BufferContext(BufferTarget target, int handle)
{
_target = target;
if (!_handles.ContainsKey(target)) _handles[target] = new Stack<int>();
_handles[target].Push(handle);
GL.BindBuffer(target, handle);
}
public void Dispose()
{
_handles[_target].Pop();
int handle = _handles[_target].Count > 0 ? _handles[_target].Peek() : 0;
GL.BindBuffer(_target, handle);
}
}
修改:注意到这个问题。如果你没有Dispose()
你的上下文对象之前没有任何后果。上下文不会切换回原来的状态。现在,如果你忘记在某种循环中处理它,你最终会得到一个 stackoverflow 。也许我应该限制堆栈大小......
答案 0 :(得分:2)
类似的策略与Asp.Net MVC和HtmlHelper一起使用。请参阅http://msdn.microsoft.com/en-us/library/system.web.mvc.html.formextensions.beginform.aspx(using (Html.BeginForm()) {....}
)
因此,对于非文档资源(如文件句柄,数据库或网络连接,字体等)明显的“需要”IDisposable,至少有一个先例可以使用此模式。我不认为它有一个特殊的名称,但在实践中,似乎是C#习惯用作C ++习语的对应物,资源获取是初始化。
当您打开文件时,您正在获取并保证处理文件上下文;在您的示例中,您正在获取的资源是一个“绑定上下文”,用您的话说。虽然我听说过“Dispose pattern”或“Using pattern”用于描述广泛的类别,但基本上“确定性清理”就是你所说的;你正在控制对象的生命周期。
我不认为它真的是一个“新”模式,它在你的用例中脱颖而出的唯一原因是显然你依赖的OpenGL实现没有特别努力匹配C#成语,这需要你建立自己的代理对象。
我唯一担心的是,是否有任何非明显的副作用,例如,如果你有一个嵌套的上下文,你的块(或调用堆栈)中有更深的类似using
结构)。
答案 1 :(得分:1)
ASP.NET / MVC使用此(可选)模式来呈现<form>
元素的开头和结尾,如下所示:
@using (Html.BeginForm()) {
<div>...</div>
}
这与您的示例类似,因为除了一次性语义之外,您不会消耗IDisposable
的值。我从来没有听说过这个名字,但我之前在其他类似场景中使用过这种东西,除了理解如何通常利用using
块{{1类似于我们如何通过实施IDisposable
来利用foreach
语义。
答案 2 :(得分:1)
我认为这更像是一种习惯而不是一种模式。模式通常更复杂,涉及多个移动部分,习语只是在代码中做事的聪明方法。
在C ++中,它使用了很多。无论何时您想要获取某些内容或进入范围,您都会创建一个自动变量(即在堆栈中)开始或创建或您需要完成的任何内容条目。当您离开声明自动变量的范围时,将调用析构函数。然后,析构函数应结束或删除或清理所需的任何内容。
class Lock {
private:
CriticalSection* criticalSection;
public:
Lock() {
criticalSection = new CriticalSection();
criticalSection.Enter();
}
~Lock() {
criticalSection.Leave();
delete criticalSection;
}
}
void F() {
Lock lock();
// Everything in here is executed in a critical section and it is exception safe.
}