【问题标题】:How DI container knows what Constructors need (ASP.NET Core)?DI 容器如何知道构造函数需要什么(ASP.NET Core)?
【发布时间】:2016-08-01 14:37:22
【问题描述】:

我已经阅读了很多关于什么是 DI 以及如何使用它的文档(与 ASP.NET Core 相关)。据我了解,当框架为我实例化某个控制器时,它以某种方式知道该控制器的类需要传递给构造函数。是反射还是什么?谁能告诉我在 ASP.NET Core GitHub 源代码的哪里可以看到它?

【问题讨论】:

    标签: c# asp.net-core dependency-injection .net-core


    【解决方案1】:

    您可以开始在 GitHub 上查找 here

    简而言之,它使用反射来检查类型的公共构造函数及其参数。

    var constructors = implementationType.GetTypeInfo()
        .DeclaredConstructors
        .Where(constructor => constructor.IsPublic)
        .ToArray();
    

    它根据参数长度对构造函数进行排序,然后选择最好的一个。

    这个 sn-p 寻找最好的构造函数来调用被实例化的类型。

    private ServiceCallSite CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType,
        CallSiteChain callSiteChain)
    {
        try
        {
            callSiteChain.Add(serviceType, implementationType);
            var constructors = implementationType.GetTypeInfo()
                .DeclaredConstructors
                .Where(constructor => constructor.IsPublic)
                .ToArray();
    
            ServiceCallSite[] parameterCallSites = null;
    
            if (constructors.Length == 0)
            {
                throw new InvalidOperationException(Resources.FormatNoConstructorMatch(implementationType));
            }
            else if (constructors.Length == 1)
            {
                var constructor = constructors[0];
                var parameters = constructor.GetParameters();
                if (parameters.Length == 0)
                {
                    return new ConstructorCallSite(lifetime, serviceType, constructor);
                }
    
                parameterCallSites = CreateArgumentCallSites(
                    serviceType,
                    implementationType,
                    callSiteChain,
                    parameters,
                    throwIfCallSiteNotFound: true);
    
                return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites);
            }
    
            Array.Sort(constructors,
                (a, b) => b.GetParameters().Length.CompareTo(a.GetParameters().Length));
    
            ConstructorInfo bestConstructor = null;
            HashSet<Type> bestConstructorParameterTypes = null;
            for (var i = 0; i < constructors.Length; i++)
            {
                var parameters = constructors[i].GetParameters();
    
                var currentParameterCallSites = CreateArgumentCallSites(
                    serviceType,
                    implementationType,
                    callSiteChain,
                    parameters,
                    throwIfCallSiteNotFound: false);
    
                if (currentParameterCallSites != null)
                {
                    if (bestConstructor == null)
                    {
                        bestConstructor = constructors[i];
                        parameterCallSites = currentParameterCallSites;
                    }
                    else
                    {
                        // Since we're visiting constructors in decreasing order of number of parameters,
                        // we'll only see ambiguities or supersets once we've seen a 'bestConstructor'.
    
                        if (bestConstructorParameterTypes == null)
                        {
                            bestConstructorParameterTypes = new HashSet<Type>(
                                bestConstructor.GetParameters().Select(p => p.ParameterType));
                        }
    
                        if (!bestConstructorParameterTypes.IsSupersetOf(parameters.Select(p => p.ParameterType)))
                        {
                            // Ambiguous match exception
                            var message = string.Join(
                                Environment.NewLine,
                                Resources.FormatAmbiguousConstructorException(implementationType),
                                bestConstructor,
                                constructors[i]);
                            throw new InvalidOperationException(message);
                        }
                    }
                }
            }
    
            if (bestConstructor == null)
            {
                throw new InvalidOperationException(
                    Resources.FormatUnableToActivateTypeException(implementationType));
            }
            else
            {
                Debug.Assert(parameterCallSites != null);
                return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites);
            }
        }
        finally
        {
            callSiteChain.Remove(serviceType);
        }
    }
    

    【讨论】:

    • 谢谢,正是我要找的东西
    【解决方案2】:

    当前 RC1 上 ASP.NET Core DI 的构造函数选择行为相当复杂。过去它只支持具有单个构造函数的类型,即very good default。然而,在 RC1 中,它接受具有多个构造函数的类型。尽管如此,它的行为还是很奇怪,在测试过程中,我没有设法让 DI 容器为我创建一个具有多个构造函数的组件。

    在幕后,构造函数的选择和构造函数参数的分析都是使用反射完成的,并构建了一个表达式树并最终编译为一个委托。代码就这么简单this:

    public Expression Build(Expression provider)
    {
        var parameters = _constructorInfo.GetParameters();
        return Expression.New(
            _constructorInfo,
            _parameterCallSites.Select((callSite, index) =>
                Expression.Convert(
                    callSite.Build(provider),
                    parameters[index].ParameterType)));
    }
    

    【讨论】:

    • 它确实支持具有多个构造函数的类型。
    • @Nkosi: 当前的 rc1not 接受具有多个构造函数的类型。
    • 我看错代码了吗?我可能误解了我正在阅读的代码。 github.com/aspnet/DependencyInjection/blob/…
    • @Nkosi:我相信你是,虽然我很难找到属于 RC1 的代码。我刚刚使用 Microsoft.AspNet.Mvc 的 1.0.0-rc1-final 版本在本地对此进行了测试。
    • @Nkosi:对不起。你其实是对的。 Core 接受多个构造函数。我现在看到了这种行为。奇怪的是,我发现实际上不可能让容器创建具有多个构造函数的类型。它不断抛出一个异常说明:“在类型'[TypeName]'中找到了接受所有给定参数类型的多个构造函数。应该只有一个适用的构造函数。”。
    猜你喜欢
    • 2018-05-30
    • 2016-11-10
    • 2020-03-10
    • 2015-08-21
    • 2021-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    相关资源
    最近更新 更多