Пользовательская реализация WeakReference - PullRequest
7 голосов
/ 17 августа 2011

WeakReference в BCL был разработан в эпоху до появления дженериков, поэтому его интерфейс не так хорош, как мог бы быть. Также свойство IsAlive очень легко использовать неправильно. Рассматривая реализацию WeakReference через Reflector, кажется, что мы могли бы реализовать это сами. Вот что я придумал:

    [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;
            }
        }
    }

но я не уверен, что правильно понял реализацию BCL. Кто-нибудь может обнаружить какие-либо проблемы в коде выше?

Ответы [ 2 ]

6 голосов
/ 12 сентября 2011

Я не замечаю ничего плохого, кроме добавления обработки ошибок.Тем не менее, я предпочитаю эту реализацию за ее простоту, тем более что она использует версию BCL, и вам не нужно так стараться, чтобы «сделать это правильно»:

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;
}
0 голосов
/ 12 сентября 2011
  • Методы GCHandle могут выдавать исключения - поэтому убедитесь, что у вас есть try / catches.
  • Вы можете поощрять более эффективное использование, предоставляя метод TryGetTarget.
  • Зачем называть это WeakRef?

Вот мой путь.

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);
        }
    }
}
...