在写代码时碰到这样一种情形:
internal static Image GetImage(XmlElement img_elem)
{
...
using(MemoryStream mem_stream = new MemoryStream(img_bytes) )
{
Image img = Image.FromStream(mem_stream);
return img;
}
...
return null;
}
如果调用者只是想从一个XML节点中保存的序列化过的Image对象, 上述函数可以满足要求, 在内部需要借助于MemoryStream, 这是个实现了IDispose接口的类, 需要关心资源的释放.
但稍后, 我发现有另一个需求, 在另一处的调用者需要得到内部的 MemoryStream从而能把原始的图像流保存到磁盘文件, img有Save方法, 但那不保证得到的是最初的文件流的内容. 这个需求面临几个问题
* 从GetImage中额外返回 MemoryStream, 返回值已经被使用, 只能通过out/ref 参数的办法
* 但返回MemoryStream带来另一个问题: 资源释放的责任被强加给了caller, 更不说如果GetImage在中途抛出异常时, caller很可能就会漏掉对 MemoryStream的 Dispose调用. 而内部的using是异常安全的.
考虑caller对 MemoryStream的需求的特征:
* 该资源是在callee 中产生并销毁的, 生存周期仅在callee中的一段时间. 而caller也仅需要在函数返回后只使用一次即释放, 并不需要长期持有该资源, 不属于save and use, 而是get-use-discard
* caller并不想关心该资源管理
* callee已经很好地管理了该资源
需要一个办法, 在callee运行过程中, 在资源的活动时间之内, 让caller能得到短时的控制权使用该资源.
回调!
internal static Image GetImage(XmlElement img_elem, ParameterizedThreadStart stream_callback)
{
...
using(MemoryStream mem_stream = new MemoryStream(img_bytes) )
{
Image img = Image.FromStream(mem_stream);
if(stream_callback != null) stream_callback(mem_stream);
return img;
}
...
return null;
}
不需要该资源的调用者可以传递null保持原来的行为. 需要该资源的调用者只需要提供一个匿名的回调函数:
using (FileStream fs = new FileStream(fname, FileMode.Open, FileAccess.Write))
{
ParameterizedThreadStart callback =
delegate(object obj)
{
MemoryStream stream = obj as MemoryStream;
ForceAssert.AlwaysAssert(stream != null,
"GetImage's ParameterizedThreadStart parameter is not MemoryStream");
stream.Position = 0;
stream.WriteTo(fs);
};
Image img = GetImage(image_elem, callback);
}
需要小心约定并遵循的是传递给ParameterizedThreadStart 回调函数的第一个参数.
如果你用过Image, 或许你已经看出上面代码里潜在的问题: Image对象产生之后, 对原始的File或MemoryStream仍有着后续的需求, 如果这些资源被释放, 当这些后续的需求产生的时候, 会有异常抛出. 这是另一个话题.
阅读(1286) | 评论(0) | 转发(0) |