Given a Disposable Immutable class that holds something sometimes big and that you don't know if there is a side effect or an exception when the object is disposed twice, (and I don't hold the code ownership to modify it to account this situation) what is the best approach how to deal with chained transformations?
Using a Bitmap as example.
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;
}
If you need to create a new bitmap with the transformation, it makes sense to take that memory, but if the transformation would have no effect (RotateFlipNone, scale = 0 or pad = 0), it makes no sense to create a new bitmap. I find myself creating clones for the sake of returning a new Disposable object on every transformation instead of returning the same input object.
The same situation would apply for example to a Date object if it would be Disposable and you would need to perform n operations, where some of them have no effect depending on the input params, (Add zero days).
The point is, some operations have no effect depending on the input parameters, still is easier to create a new object than to keep track which using is the first owner of the object and have the beforehand knowledge of the API you are using if some parameter would really create a different item or just a copy.
- Is there a pattern for this kind of situation?
- Does
usingkeep account that the object reference it holds belongs to anotherusingso it won't dispose it twice or an ObjectDisposedException would be thrown? - Is having a new object every time the safest approach even if it takes more computation and memory? (It looks like the most readable from my point of view)
An option it came up to my mind would be to have a Disposable wrapper class that ensures that the object it holds is not disposed twice, but that means that I need to know beforehand if the transformation has zero effect so I won't call it or the transformation functions know about this wrapper mechanism. Something like:
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);
}
}
And it would be used like:
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;
}
This is way bigger, confusing, requires way more knowledge of each of the transform functions (+ big list of nono reasons).
My best guess is to stay with the initial transform unless there is a real performance issue, but wondering if some elegant solution exists for:
- A function that sometimes returns a new instance and sometimes returns back the given IDisposable input parameter itself.
- Instead of returning a new instance always to avoid disposing twice
Aucun commentaire:
Enregistrer un commentaire