【发布时间】:2021-11-28 10:04:05
【问题描述】:
给定一个 Disposable Immutable 类,该类有时包含一些很大的东西,并且您不知道当对象被释放两次时是否存在副作用或异常,(而且我不持有将其修改为的代码所有权考虑这种情况)如何处理链式转换的最佳方法是什么?
以位图为例。
public static Bitmap Transform(Bitmap src, RotateFlipType rotate = RotateFlipType.RotateNoneFlipNone,
double scale = 0, int pad = 0, int alterGradient = 0)
{
using Bitmap rotated = src.Rotate(rotate);
using Bitmap scaled = MyImageUtils.ScaleBitmap(rotated, scale);
using Bitmap padded = MyImageUtils.PaddBitmap(scaled, scale);
//The owner is the caller
Bitmap result = MyImageUtils.Gradient(padded, alterGradient);
return result;
}
如果您需要使用转换创建一个新位图,那么占用该内存是有意义的,但如果转换没有效果(RotateFlipNone、scale = 0 或 pad = 0),那么创建一个没有意义新位图。我发现自己创建克隆是为了在每次转换时返回一个新的 Disposable 对象,而不是返回相同的输入对象。
例如,如果 Date 对象是 Disposable 并且您需要执行 n 次操作,则同样的情况也适用于该对象,其中一些操作无效,具体取决于输入参数(添加零天)。
关键是,某些操作对输入参数没有影响,创建新对象仍然比跟踪哪个用户是对象的第一个所有者并事先了解您正在使用的 API 更容易如果某些参数真的会创建一个不同的项目或只是一个副本。
- 这种情况有规律吗?
-
using是否考虑到它持有的对象引用属于另一个using,因此它不会释放它两次或抛出 ObjectDisposedException? - 是否每次都拥有一个新对象是最安全的方法,即使它需要更多的计算和内存? (从我的角度来看,它看起来是最易读的)
我想到的一个选择是有一个 Disposable 包装类,以确保它持有的对象不会被释放两次,但这意味着我需要事先知道转换是否具有零效应,所以我不会' t 调用它或转换函数知道此包装器机制。 比如:
public class DisposableOnce<T> : IDisposable
where T : IDisposable
{
private bool disposedValue;
public delegate void DisposedDelegate(EventArgs e);
public event DisposedDelegate? OnDisposed;
public T Value { get; }
private readonly DisposableOnce<T>? Other;
public DisposableOnce(T value)
{
Value = value;
}
public DisposableOnce(DisposableOnce<T> disposableOther)
{
Value = disposableOther.Value;
Other = disposableOther;
Other.OnDisposed += OnRefDisposed;
}
private void OnRefDisposed(EventArgs e)
{
SetDisposed();
}
public void SetDisposed()
{
disposedValue = true;
try
{
OnDisposed?.Invoke(new EventArgs());
}
catch (Exception ex)
{
//Shallow the exception to avoid propagation?
throw ex;
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Value.Dispose();
if (Other != null)
{
//Not listening you anymore
Other.OnDisposed -= OnRefDisposed;
}
}
SetDisposed();
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
它会像这样使用:
public static Bitmap Transform(Bitmap src, RotateFlipType rotate = RotateFlipType.RotateNoneFlipNone, double scale = 0, int pad = 0, int alterGradient = 0)
{
using DisposableOnce<Bitmap> rotated = new DisposableOnce<Bitmap>(src.Rotate(rotate));
using DisposableOnce<Bitmap> scaled = scale == 0 ? new DisposableOnce<Bitmap>(rotated) : new DisposableOnce<Bitmap>(MyImageUtils.ScaleBitmap(rotated.Value, scale));
using DisposableOnce<Bitmap> padded = pad == 0 ? new DisposableOnce<Bitmap>(scaled) : new DisposableOnce<Bitmap>(MyImageUtils.PaddBitmap(scaled.Value, scale));
Bitmap result;
if (alterGradient == 0)
{
//Avoid the value being disposed by the wrapper relatives
padded.SetDisposed();
result = padded.Value;
}
else
{
result = MyImageUtils.Gradient(padded.Value, alterGradient);
}
return result;
}
这要大得多,令人困惑,需要对每个变换函数有更多的了解(+ 大量的 nono 原因)。
我最好的猜测是保持初始转换,除非存在真正的性能问题,但想知道是否存在一些优雅的解决方案:
- 一个函数,有时返回一个新实例,有时返回给定的 IDisposable 输入参数本身。
- 而不是总是返回一个新实例以避免两次处理
【问题讨论】:
-
需要担心的不是两次处理(这应该不会导致问题,因为 Dispose() 的文档声明了
If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times.)。需要担心的是在对象被释放后使用它。 -
如果 C# 支持它,我建议让转换始终使用输入对象——输入将被释放,或者将在输出中重新使用。如果有人想保留输入的副本,他们会在转换消耗之前制作该副本。但是 C# 对此没有语言支持,所以我认为这不是一个好方法。我想我会尝试真的很努力不要有一次性的不可变对象。处置毕竟需要可变性 - 您正在通过处置它来改变对象的状态。
-
@MatthewWatson 给定内联使用,(我不知道优化,但我可以猜测如果没有进一步的“可见”引用,则该对象将被释放,因为编译器会忽略返回的对象可能下一个 using 语句也是一样的。对于这种情况,嵌套 using with { } 会起作用(但看起来嵌套很深)
-
@canton7 如果输入参数可以是 ref,那么您可以将引用复制到新变量并将参数设置为 null,但
using语句不允许 ref 或 out。 -
@canton7 dotnetfiddle.net/6yG6O3 ?
标签: c# design-patterns immutability disposable