File: ServiceLookup\CallSiteChain.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj (Microsoft.Extensions.DependencyInjection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Internal;
 
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    internal sealed class CallSiteChain
    {
        private readonly Dictionary<ServiceIdentifier, ChainItemInfo> _callSiteChain;
 
        public CallSiteChain()
        {
            _callSiteChain = new Dictionary<ServiceIdentifier, ChainItemInfo>();
        }
 
        public void CheckCircularDependency(ServiceIdentifier serviceIdentifier)
        {
            if (_callSiteChain.ContainsKey(serviceIdentifier))
            {
                throw new InvalidOperationException(CreateCircularDependencyExceptionMessage(serviceIdentifier));
            }
        }
 
        public void Remove(ServiceIdentifier serviceIdentifier)
        {
            _callSiteChain.Remove(serviceIdentifier);
        }
 
        public void Add(ServiceIdentifier serviceIdentifier, Type? implementationType = null)
        {
            _callSiteChain[serviceIdentifier] = new ChainItemInfo(_callSiteChain.Count, implementationType);
        }
 
        private string CreateCircularDependencyExceptionMessage(ServiceIdentifier serviceIdentifier)
        {
            var messageBuilder = new StringBuilder();
            messageBuilder.Append(SR.Format(SR.CircularDependencyException, TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType)));
            messageBuilder.AppendLine();
 
            AppendResolutionPath(messageBuilder, serviceIdentifier);
 
            return messageBuilder.ToString();
        }
 
        private void AppendResolutionPath(StringBuilder builder, ServiceIdentifier currentlyResolving)
        {
            var ordered = new List<KeyValuePair<ServiceIdentifier, ChainItemInfo>>(_callSiteChain);
            ordered.Sort((a, b) => a.Value.Order.CompareTo(b.Value.Order));
 
            foreach (KeyValuePair<ServiceIdentifier, ChainItemInfo> pair in ordered)
            {
                ServiceIdentifier serviceIdentifier = pair.Key;
                Type? implementationType = pair.Value.ImplementationType;
                if (implementationType == null || serviceIdentifier.ServiceType == implementationType)
                {
                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType));
                }
                else
                {
                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType))
                           .Append('(')
                           .Append(TypeNameHelper.GetTypeDisplayName(implementationType))
                           .Append(')');
                }
 
                builder.Append(" -> ");
            }
 
            builder.Append(TypeNameHelper.GetTypeDisplayName(currentlyResolving.ServiceType));
        }
 
        private readonly struct ChainItemInfo
        {
            public int Order { get; }
            public Type? ImplementationType { get; }
 
            public ChainItemInfo(int order, Type? implementationType)
            {
                Order = order;
                ImplementationType = implementationType;
            }
        }
    }
}