ubiq
ubiq

Reputation: 71

Custom WeakReference implementation

WeakReference in BCL was designed in pre generics era, so it's interface is not as nice as it could be. Also IsAlive property is very easy to misuse. Looking into the implementation of WeakReference trough Reflector it seems like we could implement it ourselves. Here is what I came up with:

    [SecurityPermission(Flags = SecurityPermissionFlag.UnmanagedCode)]
    public sealed class WeakRef<T> where T : class
    {
        private readonly volatile IntPtr _ptr;

        public WeakRef(T target)
            : this(target, false)
        {
        }

        [SecuritySafeCritical]
        public WeakRef(T target, bool trackResurrection)
        {
            var handle = GCHandle.Alloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
            _ptr = GCHandle.ToIntPtr(handle);
        }

        [SecuritySafeCritical]
        ~WeakRef()
        {
            var ptr = _ptr;
            if ((ptr != IntPtr.Zero) && (ptr == Interlocked.CompareExchange(ref _ptr, IntPtr.Zero, ptr)))
            {
                var handle = GCHandle.FromIntPtr(ptr);
                handle.Free();
            }
        }

        public T Target
        {
            get
            {
                var ptr = _ptr;
                if (IntPtr.Zero != ptr)
                {
                    var target = GCHandle.FromIntPtr(ptr).Target;
                    if (_ptr != IntPtr.Zero)
                    {
                        return (T)target;
                    }
                }
                return null;
            }
        }
    }

but I'm not sure I got the implementation of BCL counterpart right. Can anyone spot any issues in the code above?

Upvotes: 7

Views: 1712

Answers (2)

Kit
Kit

Reputation: 21709

I don't spot anything wrong offhand except adding error handling. However, I prefer this implementation for its simplicity, especially since it uses the BCL version and you don't have to try as hard to "get it right":

public sealed class WeakReference<T> where T : class
{
    public WeakReference(T target) : this(target, trackResurrection)
    {}

    public WeakReference(T target, bool trackResurrection)
    {
        refTarget = new WeakReference(target, trackResurrection);
    }

    public T Target { get { return refTarget.Target as T; } }

    public bool IsAlive { get { return refTarget.IsAlive; }}

    private readonly WeakReference refTarget;
}

Upvotes: 6

Jonathan Dickinson
Jonathan Dickinson

Reputation: 9218

  • The GCHandle methods could throw exceptions - so make sure you have your try/catches.
  • You could encourage better usage by providing a TryGetTarget method.
  • Why call it WeakRef?

Here is my go at it.

public sealed class WeakReference<T> : IDisposable
    where T : class
{
    private volatile IntPtr _handle;
    private GCHandleType _handleType;

    public WeakReference(T target)
        : this(target, false)
    {
    }

    [SecuritySafeCritical]
    public WeakReference(T target, bool trackResurrection)
    {
        if (target == null)
            throw new ArgumentNullException("target");
        _handleType = trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak;
        Target = target;
    }

    [SecuritySafeCritical]
    ~WeakReference()
    {
        Dispose();
    }

    public void Dispose()
    {
        var ptr = _handle;
        if ((ptr != IntPtr.Zero) &&
            Interlocked.CompareExchange(ref _handle, IntPtr.Zero, ptr) == ptr)
        {
            try
            {
                var handle = GCHandle.FromIntPtr(ptr);
                if (handle.IsAllocated)
                    handle.Free();
            }
            catch
            { }
        }
        GC.SuppressFinalize(this);
    }

    public bool TryGetTarget(out T target)
    {
        var ptr = _handle;
        if (ptr != IntPtr.Zero)
        {
            try
            {
                var handle = GCHandle.FromIntPtr(ptr);
                if (handle.IsAllocated)
                {
                    target = (T)handle.Target;
                    return !object.ReferenceEquals(target, null);
                }
            }
            catch
            { }
        }
        target = null;
        return false;
    }

    public bool TryGetTarget(out T target, Func<T> recreator)
    {
        IntPtr ptr = _handle;
        try
        {
            var handle = GCHandle.FromIntPtr(ptr);
            if (handle.IsAllocated)
            {
                target = (T)handle.Target;
                if (!object.ReferenceEquals(target, null))
                    return false;
            }
        }
        catch
        { }

        T createdValue = null;
        target = null;

        while ((ptr = _handle) == IntPtr.Zero || object.ReferenceEquals(target, null))
        {
            createdValue = createdValue ?? recreator();
            var newPointer = GCHandle.Alloc(createdValue, _handleType).AddrOfPinnedObject();
            if (Interlocked.CompareExchange(ref _handle, newPointer, ptr) == ptr)
            {
                target = createdValue;
                return true;
            }
            else if ((ptr = _handle) != IntPtr.Zero)
            {
                try
                {
                    var handle = GCHandle.FromIntPtr(ptr);
                    if (handle.IsAllocated)
                    {
                        target = (T)handle.Target;
                        if (!object.ReferenceEquals(target, null))
                            return false;
                    }
                }
                catch
                { }
            }
        }

        return false;
    }

    public bool IsAlive
    {
        get
        {
            var ptr = _handle;
            return ptr != IntPtr.Zero && GCHandle.FromIntPtr(ptr).IsAllocated;
        }
    }

    public T Target
    {
        get
        {
            T target;
            TryGetTarget(out target);
            return target;
        }
        set
        {
            Dispose();
            _handle = GCHandle.Alloc(value, _handleType).AddrOfPinnedObject();
            GC.ReRegisterForFinalize(this);
        }
    }
}

Upvotes: 0

Related Questions