diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index ddd8c9e2ff9d53..1715c7b7a39245 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -116,7 +117,34 @@ public abstract class ContractRegistry /// public virtual IDebugger Debugger => GetContract(); - public abstract TContract GetContract() where TContract : IContract; + /// + /// Attempts to get an instance of the requested contract for the target. + /// + /// The contract type to retrieve. + /// + /// When this method returns , contains the requested contract instance; otherwise, . + /// + /// + /// When this method returns , contains a human-readable explanation of why the contract could not be retrieved; otherwise, . + /// + /// + /// if the requested contract is present and was retrieved successfully; if the contract is not present or registered"/>. + /// + public abstract bool TryGetContract([NotNullWhen(true)] out TContract contract, out string? failureReason) where TContract : IContract; + + public TContract GetContract() where TContract : IContract + { + if (!TryGetContract(out TContract contract, out string? failureReason)) + { + throw new NotImplementedException($"Contract '{typeof(TContract).Name}' is not supported by the target. Reason: {failureReason ?? "no reason provided"}"); + } + return contract; + } + + public bool TryGetContract([NotNullWhen(true)] out TContract contract) where TContract : IContract + { + return TryGetContract(out contract, out _); + } /// /// Register a contract implementation for a specific version. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 10c9497447efb5..ca735474a15a7f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader; @@ -37,22 +38,36 @@ public override void Register(int version, Func cr _creators[(typeof(TContract), version)] = t => creator(t); } - public override TContract GetContract() + public override bool TryGetContract([NotNullWhen(true)] out TContract contract, out string? failureReason) { + contract = default!; + failureReason = null; if (_contracts.TryGetValue(typeof(TContract), out IContract? cached)) - return (TContract)cached; + { + contract = (TContract)cached; + return true; + } if (!_tryGetContractVersion(TContract.Name, out int version)) - throw new NotImplementedException($"Contract '{TContract.Name}' is not present in the contract descriptor."); + { + failureReason = $"Target does not support contract '{typeof(TContract).Name}'."; + return false; + } if (!_creators.TryGetValue((typeof(TContract), version), out Func? creator)) - throw new NotImplementedException($"No implementation registered for contract '{TContract.Name}' version {version}."); + { + failureReason = $"Target supports contract '{typeof(TContract).Name}' version {version}, but no implementation is registered for that version."; + return false; + } - TContract contract = (TContract)creator(_target); + contract = (TContract)creator(_target); if (_contracts.TryAdd(typeof(TContract), contract)) - return contract; + { + return true; + } - return (TContract)_contracts[typeof(TContract)]; + contract = (TContract)_contracts[typeof(TContract)]; + return true; } public override void Flush() diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 8e9fc73072acaf..fdbe316b65fa68 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -521,30 +521,40 @@ public void SetMock(TContract mock) where TContract : IContract public override void Register(int version, Func creator) => _creators[(typeof(TContract), version)] = t => creator(t); - public override TContract GetContract() + public override bool TryGetContract([NotNullWhen(true)] out TContract contract, out string? failureReason) { + contract = default!; + failureReason = null; if (_resolved.TryGetValue(typeof(TContract), out var cached)) - return (TContract)cached; + { + contract = (TContract)cached; + return true; + } - IContract contract; + IContract resolved; if (_mocks.TryGetValue(typeof(TContract), out var mock)) { - contract = mock; + resolved = mock; } else if (_versions.TryGetValue(typeof(TContract), out int version)) { if (!_creators.TryGetValue((typeof(TContract), version), out var creator)) - throw new NotImplementedException($"No implementation registered for contract '{typeof(TContract).Name}' version {version}."); + { + failureReason = $"Target supports contract '{typeof(TContract).Name}' version {version}, but no implementation is registered for that version."; + return false; + } - contract = creator(_target); + resolved = creator(_target); } else { - throw new NotImplementedException($"Contract {typeof(TContract).Name} is not registered. Use SetVersion(version) or SetMock(mock) to configure contracts."); + failureReason = $"Contract '{typeof(TContract).Name}' is not supported by the target."; + return false; } - _resolved[typeof(TContract)] = contract; - return (TContract)contract; + _resolved[typeof(TContract)] = resolved; + contract = (TContract)resolved; + return true; } public override void Flush() { }