| File: System\ComponentModel\Design\Serialization\MemberRelationshipService.cs | Web Access |
| Project: src\runtime\src\libraries\System.ComponentModel.TypeConverter\src\System.ComponentModel.TypeConverter.csproj (System.ComponentModel.TypeConverter) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.ComponentModel.Design.Serialization { /// <summary> /// A member relationship service is used by a serializer to announce that one /// property is related to a property on another object. Consider a code /// based serialization scheme where code is of the following form: /// /// object1.Property1 = object2.Property2 /// /// Upon interpretation of this code, Property1 on object1 will be /// set to the return value of object2.Property2. But the relationship /// between these two objects is lost. Serialization schemes that /// wish to maintain this relationship may install a MemberRelationshipService /// into the serialization manager. When an object is deserialized /// this service will be notified of these relationships. It is up to the service /// to act on these notifications if it wishes. During serialization, the /// service is also consulted. If a relationship exists the same /// relationship is maintained by the serializer. /// </summary> public abstract class MemberRelationshipService { private readonly Dictionary<RelationshipEntry, RelationshipEntry> _relationships = new Dictionary<RelationshipEntry, RelationshipEntry>(); /// <summary> /// Returns the current relationship associated with the source, or MemberRelationship.Empty if /// there is no relationship. Also sets a relationship between two objects. Empty /// can also be passed as the property value, in which case the relationship will /// be cleared. /// </summary> public MemberRelationship this[MemberRelationship source] { get { // The Owner and Member properties can be null if the MemberRelationship is constructed // using the default constructor. However there is no situation in which one is null // and not the other as the main constructor performs argument validation. if (source.Owner == null) { throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "source.Owner"), nameof(source)); } Debug.Assert(source.Member != null); return GetRelationship(source); } set { // The Owner and Member properties can be null if the MemberRelationship is constructed // using the default constructor. However there is no situation in which one is null // and not the other as the main constructor performs argument validation. if (source.Owner == null) { throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "source.Owner"), nameof(source)); } Debug.Assert(source.Member != null); SetRelationship(source, value); } } /// <summary> /// Returns the current relationship associated with the source, or null if there is no relationship. /// Also sets a relationship between two objects. Null can be passed as the property value, in which /// case the relationship will be cleared. /// </summary> public MemberRelationship this[object sourceOwner, MemberDescriptor sourceMember] { get { ArgumentNullException.ThrowIfNull(sourceOwner); ArgumentNullException.ThrowIfNull(sourceMember); return GetRelationship(new MemberRelationship(sourceOwner, sourceMember)); } set { ArgumentNullException.ThrowIfNull(sourceOwner); ArgumentNullException.ThrowIfNull(sourceMember); SetRelationship(new MemberRelationship(sourceOwner, sourceMember), value); } } /// <summary> /// This is the implementation API for returning relationships. The default implementation stores the /// relationship in a table. Relationships are stored weakly, so they do not keep an object alive. /// </summary> protected virtual MemberRelationship GetRelationship(MemberRelationship source) { if (_relationships.TryGetValue(new RelationshipEntry(source), out RelationshipEntry retVal) && retVal._owner.IsAlive) { return new MemberRelationship(retVal._owner.Target!, retVal._member); } return MemberRelationship.Empty; } /// <summary> /// This is the implementation API for returning relationships. The default implementation stores the /// relationship in a table. Relationships are stored weakly, so they do not keep an object alive. Empty can be /// passed in for relationship to remove the relationship. /// </summary> protected virtual void SetRelationship(MemberRelationship source, MemberRelationship relationship) { if (!relationship.IsEmpty && !SupportsRelationship(source, relationship)) { ThrowRelationshipNotSupported(source, relationship); } _relationships[new RelationshipEntry(source)] = new RelationshipEntry(relationship); } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "GetComponentName is only used to create a nice exception message, and has a fallback when null is returned.")] private static void ThrowRelationshipNotSupported(MemberRelationship source, MemberRelationship relationship) { string? sourceName = TypeDescriptor.GetComponentName(source.Owner!) ?? source.Owner!.ToString(); string? relName = TypeDescriptor.GetComponentName(relationship.Owner!) ?? relationship.Owner!.ToString(); throw new ArgumentException(SR.Format(SR.MemberRelationshipService_RelationshipNotSupported, sourceName, source.Member.Name, relName, relationship.Member.Name)); } /// <summary> /// Returns true if the provided relationship is supported. /// </summary> public abstract bool SupportsRelationship(MemberRelationship source, MemberRelationship relationship); /// <summary> /// Used as storage in our relationship table /// </summary> private struct RelationshipEntry : IEquatable<RelationshipEntry> { internal WeakReference _owner; internal MemberDescriptor _member; private readonly int _hashCode; internal RelationshipEntry(MemberRelationship rel) { _owner = new WeakReference(rel.Owner); _member = rel.Member; _hashCode = rel.Owner == null ? 0 : rel.Owner.GetHashCode(); } public override bool Equals([NotNullWhen(true)] object? o) { Debug.Assert(o is RelationshipEntry, "This is only called indirectly from a dictionary only containing RelationshipEntry structs."); return Equals((RelationshipEntry)o); } public bool Equals(RelationshipEntry other) { object? owner1 = (_owner.IsAlive ? _owner.Target : null); object? owner2 = (other._owner.IsAlive ? other._owner.Target : null); return owner1 == owner2 && _member.Equals(other._member); } public static bool operator ==(RelationshipEntry re1, RelationshipEntry re2) => re1.Equals(re2); public static bool operator !=(RelationshipEntry re1, RelationshipEntry re2) => !re1.Equals(re2); public override int GetHashCode() => _hashCode; } } /// <summary> /// This class represents a single relationship between an object and a member. /// </summary> public readonly struct MemberRelationship : IEquatable<MemberRelationship> { public static readonly MemberRelationship Empty; /// <summary> /// Creates a new member relationship. /// </summary> public MemberRelationship(object owner, MemberDescriptor member) { ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(member); Owner = owner; Member = member; } /// <summary> /// Returns true if this relationship is empty. /// </summary> public bool IsEmpty => Owner == null; /// <summary> /// The member in this relationship. /// </summary> public MemberDescriptor Member { get; } /// <summary> /// The object owning the member. /// </summary> public object? Owner { get; } /// <summary> /// Infrastructure support to make this a first class struct /// </summary> public override bool Equals([NotNullWhen(true)] object? obj) => obj is MemberRelationship rel && Equals(rel); /// <summary> /// Infrastructure support to make this a first class struct /// </summary> public bool Equals(MemberRelationship other) => other.Owner == Owner && other.Member == Member; /// <summary> /// Infrastructure support to make this a first class struct /// </summary> public override int GetHashCode() => Owner is null ? base.GetHashCode() : Owner.GetHashCode() ^ Member.GetHashCode(); /// <summary> /// Infrastructure support to make this a first class struct /// </summary> public static bool operator ==(MemberRelationship left, MemberRelationship right) => left.Equals(right); /// <summary> /// Infrastructure support to make this a first class struct /// </summary> public static bool operator !=(MemberRelationship left, MemberRelationship right) => !left.Equals(right); } }