|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using static System.WeakReferenceHandleTags;
namespace System
{
[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
// This class is sealed to mitigate security issues caused by Object::MemberwiseClone.
public sealed partial class WeakReference<T> : ISerializable
where T : class?
{
// If you fix bugs here, please fix them in WeakReference at the same time.
// Most methods using the handle should use GC.KeepAlive(this) to avoid potential handle recycling
// attacks (i.e. if the WeakReference instance is finalized away underneath you when you're still
// handling a cached value of the handle then the handle could be freed and reused).
private nint _taggedHandle;
// Creates a new WeakReference that keeps track of target.
// Assumes a Short Weak Reference (ie TrackResurrection is false.)
public WeakReference(T target)
: this(target, false)
{
}
// Creates a new WeakReference that keeps track of target.
//
public WeakReference(T target, bool trackResurrection)
{
Create(target, trackResurrection);
}
private WeakReference(SerializationInfo info, StreamingContext context)
{
ArgumentNullException.ThrowIfNull(info);
T target = (T)info.GetValue("TrackedObject", typeof(T))!; // Do not rename (binary serialization)
bool trackResurrection = info.GetBoolean("TrackResurrection"); // Do not rename (binary serialization)
Create(target, trackResurrection);
}
//
// We are exposing TryGetTarget instead of a simple getter to avoid a common problem where people write incorrect code like:
//
// WeakReference ref = ...;
// if (ref.Target != null)
// DoSomething(ref.Target)
//
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetTarget([MaybeNullWhen(false), NotNullWhen(true)] out T target)
{
T? o = this.Target;
target = o!;
return o != null;
}
[Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
ArgumentNullException.ThrowIfNull(info);
info.AddValue("TrackedObject", this.Target, typeof(T)); // Do not rename (binary serialization)
info.AddValue("TrackResurrection", IsTrackResurrection()); // Do not rename (binary serialization)
}
// Returns a boolean indicating whether or not we're tracking objects until they're collected (true)
// or just until they're finalized (false).
private bool IsTrackResurrection() => (_taggedHandle & TracksResurrectionBit) != 0;
// Creates a new WeakReference that keeps track of target.
private void Create(T target, bool trackResurrection)
{
nint h = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
_taggedHandle = trackResurrection ?
h | TracksResurrectionBit :
h;
#if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS
ComAwareWeakReference.ComInfo? comInfo = ComAwareWeakReference.ComInfo.FromObject(target);
if (comInfo != null)
{
ComAwareWeakReference.SetComInfoInConstructor(ref _taggedHandle, comInfo);
}
#endif
}
public void SetTarget(T target)
{
nint th = _taggedHandle & ~TracksResurrectionBit;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (th == 0)
throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized);
#if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS
var comInfo = ComAwareWeakReference.ComInfo.FromObject(target);
if ((th & ComAwareBit) != 0 || comInfo != null)
{
ComAwareWeakReference.SetTarget(ref _taggedHandle, target, comInfo);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return;
}
#endif
GCHandle.InternalSet(th, target);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
}
private T? Target
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
nint th = _taggedHandle & ~TracksResurrectionBit;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (th == 0)
return default;
T? target;
#if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS
if ((th & ComAwareBit) != 0)
{
target = Unsafe.As<T?>(ComAwareWeakReference.GetTarget(th));
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
#endif
// unsafe cast is ok as the handle cannot be destroyed and recycled while we keep the instance alive
target = Unsafe.As<T?>(GCHandle.InternalGet(th));
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
}
// Note: While WeakReference<T> is formally a finalizable type, the finalizer does not actually run.
// Instead the instances are treated specially in GC when scanning for no longer strongly-reachable
// finalizable objects.
#pragma warning disable CA1821 // Remove empty Finalizers
~WeakReference()
{
Debug.Fail(" WeakReference<T> finalizer should never run");
}
#pragma warning restore CA1821 // Remove empty Finalizers
}
}
|