处理不可变的一次性对象

Dealing with Immutable disposable objects

给定一个 Disposable Immutable class,它有时会容纳一些大的东西,并且当对象被处置两次时你不知道是否有副作用或异常,(我不持有代码所有权以修改它以说明这种情况)处理链式转换的最佳方法是什么?

以位图为例

        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 如果某些参数真的会创建一个不同的项目或只是一个副本,您正在使用。

我想到的一个选择是拥有一个一次性包装器 class 以确保它所持有的对象不会被处置两次,但这意味着我需要事先知道转换是否具有零效果所以我不会调用它或者转换函数知道这个包装器机制。 类似于:

    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 原因)。

我最好的猜测是保留初始转换,除非存在真正的性能问题,但想知道是否存在一些优雅的解决方案:

我认为一次性对象并不是真正不可变的。如果它们在哪里,返回原始对象或新对象就不会有任何问题。因为它不是不可变的,所以我认为你应该总是做同样的事情,并且永远不要试图通过返回原始对象来优化。

Is there a pattern for this kind of situation?

有一些模式可以减少创建新对象的影响

  1. 就地修改对象 - 这可能是开销最少的最简单的选项。但它会使代码更难理解,并且它可能并不总是适用。
  2. 对象池化 - 保留可重复使用的图像列表以避免创建对象的开销。这可以允许“冰棒不变性”,您可以在其中修改对象然后将其冻结,仅在将其返回到池中时才将其解冻。
  3. 让调用者提供将结果写入的对象 - 这将对象分配的责任从方法转移到调用者,调用者应该处于更好的位置决定最优策略。

Does using keep account that the object reference it holds belongs to another using so it won't dispose it twice or an ObjectDisposedException would be thrown?

不,using 只是 try{....} finally{myObject.Dispose()} 的 shorthand。然而,设计良好的对象不应该关心它们是否被处理了两次。如果某些第三方对象不遵循这种做法,则使用包装器可能是合理的。但是大多数对象不需要这样的包装器,我会做这样一个包装器比你建议的要简单得多,因为它只需要一个标志来判断它是否已经被处理。

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)

我认为不可变对象更易于使用和理解,这需要创建新对象而不是改变它。但是,它确实会影响性能。这种影响是否显着取决于具体应用。在进行图像处理等操作时,易用性的优先级低于性能是很常见的。