Is there a boost::shared_ptr equivalent in C#?

前端 未结 5 479
生来不讨喜
生来不讨喜 2021-01-19 05:33

Just curious, I\'ve been using boost:shared_ptr\'s in the past a LOT - for having multiple objects store a shared pointer to a single object etc.

Is there an equival

5条回答
  •  不思量自难忘°
    2021-01-19 06:30

    The GC eliminates the need for shared pointers when it comes to memory management, but it doesn't do this for resource management.

    IDisposable is .NET's solution for resource managemenet, but it doesn't allow shared ownership semantics. See this article for a thorough discussion of the weaknesses.

    Here's a simple implementation of a SharedRef, which follows the boost::shared_ptr pattern of a heap allocated reference count object. Not sure how useful it will be yet, but feel free to comment and improve it...

    /// 
    /// SharedRef class, which implements reference counted IDisposable ownership.
    /// See also the static helper class for an easier construction syntax.
    /// 
    public class SharedRef : IDisposable
        where T:class,IDisposable
    {
        private SharedRefCounter _t;
    
        /// 
        /// Create a SharedRef directly from an object. Only use this once per object.
        /// After that, create SharedRefs from previous SharedRefs.
        /// 
        /// 
        public SharedRef(T t)
        {
            _t = new SharedRefCounter(t);
            _t.Retain();
        }
    
        /// 
        /// Create a SharedRef from a previous SharedRef, incrementing the reference count.
        /// 
        /// 
        public SharedRef(SharedRef o)
        {
            o._t.Retain();
            _t = o._t;
        }
    
        public static SharedRef Create(T t)
        {
            return new SharedRef(t);
        }
    
        private bool _disposed = false;
    
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
    
            if (disposing)
            {
                if (_t != null)
                {
                    _t.Release();
                    _t = null;
                }
            }
    
            _disposed = true;
        }
    
        public void Dispose()
        {
            Dispose(true);
        }
    
        public T Get()
        {
            return _t.Get();
        }
    }
    
    /// 
    /// Static helper class for easier construction syntax.
    /// 
    public static class SharedRef
    {
        /// 
        /// Create a SharedRef directly from an object. Only use this once per object.
        /// After that, create SharedRefs from previous SharedRefs.
        /// 
        /// 
        public static SharedRef Create(T t) where T : class,IDisposable
        {
            return new SharedRef(t);
        }
    
        /// 
        /// Create a SharedRef from a previous SharedRef, incrementing the reference count.
        /// 
        /// 
        public static SharedRef Create(SharedRef o) where T : class,IDisposable
        {
            return new SharedRef(o);
        }
    }
    
    /// 
    /// Class which holds the reference count for a shared object.
    /// 
    /// 
    internal class SharedRefCounter where T : class,IDisposable
    {
        private int _count;
        private readonly T _t;
    
        public T Get()
        {
            return _t;
        }
    
        public SharedRefCounter(T t)
        {
            _count = 0;
            _t = t;
        }
    
        /// 
        /// Decrement the reference count, Dispose target if reaches 0
        /// 
        public void Release()
        {
            lock (_t)
            {
                if (--_count == 0)
                {
                    _t.Dispose();
                }
            }
        }
    
        /// 
        /// Increment the reference count
        /// 
        public void Retain()
        {
            lock (_t)
            {
                ++_count;
            }
        }
    }
    

    Notes:

    1. To ensure there's only one reference count for each shared object, make sure you only create 1 SharedRef directly from the object, and after that, create new SharedRefs from previous SharedRefs. This is the same for boost::shared_ptr. It would be nice to add some protection to the class in case you forget this.
    2. It seems a shame that the SharedRef itself has to be a reference type, allocated on the heap, but I think this is the only way of making it Disposable.
    3. I haven't implemented the equivalent of weak_ptr yet, but I think that could easily be added.
    4. It's thread safe (I think), but could probably be more efficient as it uses locks.

    Here's some test code showing it in action across 3 threads.

    [TestFixture]
    public class SharedRefTest
    {
        public class MyDisposable : IDisposable
        {
            private bool _disposed = false;
            private string _id;
    
            public MyDisposable(string id) { _id = id; }
    
            protected virtual void Dispose(bool disposing)
            {
                if (!_disposed)
                {
                    if (disposing)
                    {
                        Console.WriteLine("{0}: Disposing {1}", Thread.CurrentThread.ManagedThreadId, _id);
                    }
    
                    _disposed = true;
                }
            }
    
            public void Dispose()
            {
                Dispose(true);
            }
    
            public override string ToString()
            {
                return _id;
            }
        }
    
        [Test]
        public void Run()
        {
            Task t1, t2, t3;
    
            // create 2 objects
            Console.WriteLine("{0}: starting initial scope", Thread.CurrentThread.ManagedThreadId);
            using (var o1 = SharedRef.Create(new MyDisposable("o1")))
            using (var o2 = SharedRef.Create(new MyDisposable("o2")))
            {
                // and 3 sharedrefs, which will be Disposed in 3 separate threads
                var p1 = SharedRef.Create(o1);
                var p2a = SharedRef.Create(o2);
                var p2b = SharedRef.Create(o2);
                t1 = Task.Run(() =>
                {
                    using (p1)
                    {
                        Console.WriteLine("{0}: in another thread, using o1", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(1000);
                        Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                    }
                });
                t2 = Task.Run(() =>
                {
                    using (p2a)
                    {
                        Console.WriteLine("{0}: in another thread, using o2", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(1000);
                        Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                    }
                });
                t3 = Task.Run(() =>
                {
                    using (p2b)
                    {
                        Console.WriteLine("{0}: in another thread, using o2", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(1000);
                        Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                    }
                });
                Console.WriteLine("{0}: exiting initial scope", Thread.CurrentThread.ManagedThreadId);
            }
            t1.Wait();
            t2.Wait();
            t3.Wait();
        }
    }
    

提交回复
热议问题