0

给定一个 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 包装类,以确保它持有的对象不会被释放两次,但这意味着我需要事先知道转换是否具有零效应,所以我不会调用它或者转换函数知道这个包装机制。就像是:

    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 输入参数本身的函数。
  • 而不是总是返回一个新实例以避免处理两次
4

1 回答 1

3

我认为一次性对象并不是真正不可变的。如果它们在哪里,则返回原始对象或新对象不会有任何问题。由于它不是一成不变的,我认为您应该始终做同样的事情,并且永远不要尝试通过返回原始对象来进行优化。

这种情况有模式吗?

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

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

using 是否保留它所持有的对象引用属于另一个 using,因此它不会两次处理它或抛出 ObjectDisposedException ?

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

即使需要更多的计算和内存,每次都拥有一个新对象是最安全的方法吗?(从我的角度来看,它看起来是最易读的)

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

于 2021-10-08T14:36:30.067 回答