| File: System\Composition\Hosting\Core\CompositionContract.cs | Web Access |
| Project: src\runtime\src\libraries\System.Composition.Runtime\src\System.Composition.Runtime.csproj (System.Composition.Runtime) |
// 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; using System.Collections.Generic; using System.Composition.Runtime.Util; using System.Linq; namespace System.Composition.Hosting.Core { /// <summary> /// The link between exports and imports. /// </summary> public sealed class CompositionContract { private readonly Type _contractType; private readonly string _contractName; private readonly IDictionary<string, object> _metadataConstraints; /// <summary> /// Construct a <see cref="CompositionContract"/>. /// </summary> /// <param name="contractType">The type shared between the exporter and importer.</param> public CompositionContract(Type contractType) : this(contractType, null) { } /// <summary> /// Construct a <see cref="CompositionContract"/>. /// </summary> /// <param name="contractType">The type shared between the exporter and importer.</param> /// <param name="contractName">Optionally, a name that discriminates this contract from others with the same type.</param> public CompositionContract(Type contractType, string contractName) : this(contractType, contractName, null) { } /// <summary> /// Construct a <see cref="CompositionContract"/>. /// </summary> /// <param name="contractType">The type shared between the exporter and importer.</param> /// <param name="contractName">Optionally, a name that discriminates this contract from others with the same type.</param> /// <param name="metadataConstraints">Optionally, a non-empty collection of named constraints that apply to the contract.</param> public CompositionContract(Type contractType, string contractName, IDictionary<string, object> metadataConstraints) { if (contractType == null) throw new ArgumentNullException(nameof(contractType)); if (metadataConstraints?.Count == 0) throw new ArgumentOutOfRangeException(nameof(metadataConstraints)); _contractType = contractType; _contractName = contractName; _metadataConstraints = metadataConstraints; } /// <summary> /// The type shared between the exporter and importer. /// </summary> public Type ContractType => _contractType; /// <summary> /// A name that discriminates this contract from others with the same type. /// </summary> public string ContractName => _contractName; /// <summary> /// Constraints applied to the contract. Instead of using this collection /// directly it is advisable to use the <see cref="TryUnwrapMetadataConstraint"/> method. /// </summary> public IEnumerable<KeyValuePair<string, object>> MetadataConstraints => _metadataConstraints; /// <summary> /// Determines equality between two contracts. /// </summary> /// <param name="obj">The contract to test.</param> /// <returns>True if the contracts are equivalent; otherwise, false.</returns> public override bool Equals(object obj) { var contract = obj as CompositionContract; return contract != null && contract._contractType.Equals(_contractType) && (_contractName == null ? contract._contractName == null : _contractName.Equals(contract._contractName)) && ConstraintEqual(_metadataConstraints, contract._metadataConstraints); } /// <summary> /// Gets a hash code for the contract. /// </summary> /// <returns>The hash code.</returns> public override int GetHashCode() { var hc = _contractType.GetHashCode(); if (_contractName != null) hc ^= _contractName.GetHashCode(); if (_metadataConstraints != null) hc ^= ConstraintHashCode(_metadataConstraints); return hc; } /// <summary> /// Creates a string representation of the contract. /// </summary> /// <returns>A string representation of the contract.</returns> public override string ToString() { var result = Formatters.Format(_contractType); if (_contractName != null) result += " " + Formatters.Format(_contractName); if (_metadataConstraints != null) result += string.Format(" {{ {0} }}", string.Join(SR.Formatter_ListSeparatorWithSpace, _metadataConstraints.Select(kv => $"{kv.Key} = {Formatters.Format(kv.Value)}"))); return result; } /// <summary> /// Transform the contract into a matching contract with a /// new contract type (with the same contract name and constraints). /// </summary> /// <param name="newContractType">The contract type for the new contract.</param> /// <returns>A matching contract with a /// new contract type.</returns> public CompositionContract ChangeType(Type newContractType) { if (newContractType == null) throw new ArgumentNullException(nameof(newContractType)); return new CompositionContract(newContractType, _contractName, _metadataConstraints); } /// <summary> /// Check the contract for a constraint with a particular name and value, and, if it exists, /// retrieve both the value and the remainder of the contract with the constraint /// removed. /// </summary> /// <typeparam name="T">The type of the constraint value.</typeparam> /// <param name="constraintName">The name of the constraint.</param> /// <param name="constraintValue">The value if it is present and of the correct type, otherwise null.</param> /// <param name="remainingContract">The contract with the constraint removed if present, otherwise null.</param> /// <returns>True if the constraint is present and of the correct type, otherwise false.</returns> public bool TryUnwrapMetadataConstraint<T>(string constraintName, out T constraintValue, out CompositionContract remainingContract) { ArgumentNullException.ThrowIfNull(constraintName); constraintValue = default(T); remainingContract = null; if (_metadataConstraints == null) return false; if (!_metadataConstraints.TryGetValue(constraintName, out object value)) return false; if (!(value is T)) return false; constraintValue = (T)value; if (_metadataConstraints.Count == 1) { remainingContract = new CompositionContract(_contractType, _contractName); } else { var remainingConstraints = new Dictionary<string, object>(_metadataConstraints); remainingConstraints.Remove(constraintName); remainingContract = new CompositionContract(_contractType, _contractName, remainingConstraints); } return true; } internal static bool ConstraintEqual(IDictionary<string, object> first, IDictionary<string, object> second) { if (first == second) return true; if (first == null || second == null) return false; if (first.Count != second.Count) return false; foreach (KeyValuePair<string, object> firstItem in first) { if (!second.TryGetValue(firstItem.Key, out object secondValue)) return false; if (firstItem.Value == null) { return secondValue == null; } else if (secondValue == null) { return false; } else { if (firstItem.Value is IEnumerable firstEnumerable && !(firstEnumerable is string)) { var secondEnumerable = secondValue as IEnumerable; if (secondEnumerable == null || !Enumerable.SequenceEqual(firstEnumerable.Cast<object>(), secondEnumerable.Cast<object>())) return false; } else if (!firstItem.Value.Equals(secondValue)) { return false; } } } return true; } private static int ConstraintHashCode(IDictionary<string, object> metadata) { var result = -1; foreach (KeyValuePair<string, object> kv in metadata) { result ^= kv.Key.GetHashCode(); if (kv.Value != null) { if (kv.Value is string sval) { result ^= sval.GetHashCode(); } else { if (kv.Value is IEnumerable enumerableValue) { foreach (var ev in enumerableValue) if (ev != null) result ^= ev.GetHashCode(); } else { result ^= kv.Value.GetHashCode(); } } } } return result; } } }