带有类型参数的.net Core 3.0 GetMethod不起作用

时间:2020-01-19 02:41:31

标签: c# types comparison .net-core-3.0

我正在尝试加载包含带有IServiceCollection参数的静态方法的插件程序集。我已经成功加载了程序集并获得了包含方法的类型(t如下),但是如果我包含types参数,则无法使用GetMethod()获取该方法。

methodInfo = t.GetMethod(mappingConfig.MethodName, BindingFlags.Public | BindingFlags.Static, null, 
  new Type[] { typeof(IServiceCollection) }, null);

有趣的是,我可以使用

methodInfo = t.GetMethod(mappingConfig.MethodName, BindingFlags.Public | BindingFlags.Static); 

但是,如果我尝试验证方法的参数,则类型比较似乎会由于没有可解释的原因而失败。

methodInfo = t.GetMethod(mappingConfig.MethodName, BindingFlags.Public | BindingFlags.Static);

methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection); // Returns false
methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection)); // Returns false
methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection)); // Returns false

静态方法如下:

public static class CustomFieldsEntityMapBuilderPlugin
{
  public static void AddCustomFieldMaps(IServiceCollection services)
  {
    var builder = new EntityMapBuilder(services);
    builder.AddCustomFieldsToMaps<AxEntities.CustomerV3, CRMEntities.lev_customer>();
  }
}

当我将两种类型分配给变量并检查它们时,我看不到任何区别:

var parmType = methodInfo.GetParameters()[0].ParameterType;
var iServiceCollectionType = typeof(IServiceCollection);

parmType.FullName == iServiceCollectionType.FullName; // true
parmType.AssemblyQualifiedName ==  iServiceCollectionType.AssemblyQualifiedName; //true

// parmType.FullName == Microsoft.Extensions.DependencyInjection.IServiceCollection
// iServiceCollectionType.FullName == Microsoft.Extensions.DependencyInjection.IServiceCollection
// parmType.AssemblyQualifiedName == Microsoft.Extensions.DependencyInjection.IServiceCollection, Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// iServiceCollectionType.AssemblyQualifiedName == Microsoft.Extensions.DependencyInjection.IServiceCollection, Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60

在我看来,导致这些类型不相等的任何原因都导致GetMethod()失败。

似乎我必须丢失一些东西。任何帮助表示赞赏。我想确保我得到的方法带有正确的参数。

更新

我已经可以通过一个小型控制台应用程序重现此问题。如果我在插件中使用方法,则反射似乎会以不同的方式对待IServiceCollection类型,即使它们来自同一程序集,也可能在不同的上下文中加载。

由于我被要求共享完整的代码,而且我不知道如何上传,所以我现在将其发布在问题中。

来自TestGetMethod.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.2" />
  </ItemGroup>

</Project>

Program.cs

using System;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using System.IO;

namespace TestGetMethod
{
    class Program
    {
        static void Main(string[] args)
        {
            var assembly = Assembly.GetExecutingAssembly();
            Type t = assembly.GetType($"TestGetMethod.{nameof(CustomFieldsEntityMapBuilderPlugin)}");

            MethodInfo methodInfo = t.GetMethod("AddCustomFieldMaps",
                BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(IServiceCollection) }, null);

            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection) Returns {methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection)}");
            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection)) Returns {methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection))}");
            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection)) Returns {methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection))}");

            assembly = LoadPlugin();
            t = assembly.GetType($"PluginLibrary.{nameof(CustomFieldsEntityMapBuilderPlugin)}");
            methodInfo = t.GetMethod("AddCustomFieldMaps",
                BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(IServiceCollection) }, null);
            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection) Returns {methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection)}");
            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection)) Returns {methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection))}");
            Console.WriteLine($"methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection)) Returns {methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection))}");
        }
        static Assembly LoadPlugin()
        {
            string pluginLocation = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "PluginLibrary.dll");
            Console.WriteLine($"Loading commands from: {pluginLocation}");
            PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
            return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
        }
    }
}

PluginLoadContext.cs

using System;
using System.Reflection;
using System.Runtime.Loader;

namespace TestGetMethod
{
    class PluginLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;

        public PluginLoadContext(string pluginPath)
        {
            _resolver = new AssemblyDependencyResolver(pluginPath);
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (libraryPath != null)
            {
                return LoadUnmanagedDllFromPath(libraryPath);
            }

            return IntPtr.Zero;
        }
    }
}

CustomFielsEntityMapBuilderPlugin.cs

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;


namespace TestGetMethod
{
    public static class CustomFieldsEntityMapBuilderPlugin
    {
        public static void AddCustomFieldMaps(IServiceCollection services)
        {
            Console.WriteLine("InAddCustomerFieldMaps");
        }
    }
}

来自PluginLibrary.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.2" />
  </ItemGroup>

</Project>

CustomFieldsEntityMapBuilderPlugin.cs

using System;
using Microsoft.Extensions.DependencyInjection;

namespace PluginLibrary
{
    public static class CustomFieldsEntityMapBuilderPlugin
    {
        public static void AddCustomFieldMaps(IServiceCollection services)
        {
            Console.WriteLine("InAddCustomerFieldMaps");
        }
    }
}

我构建了PluginLibrary并将其部署到与TestGetMethod程序集相同的位置。

如果您查看program.main,我将加载执行程序集并使用TestGetMethod命名空间从当前程序集中获取CustomFieldsEntityMapBuilderPlugin类。当我将TestGetMethod.CustomFieldsEntityMapBuilderPlugin.AddCustomFieldMaps(IServiceCollection services)参数类型与typeof(IServiceCollection)比较时,参数是相同类型。

然后我调用LoadPlugin(),它创建一个新的上下文并将PluginLibrary.dll加载到该上下文中,并将其作为程序集返回。然后,我从带有PluginLibrary命名空间的PluginLibrary程序集中获得CustomFieldsEntityMapBuilderPlugin类。当我将PluginLibrary.CustomFieldsEntityMapBuilderPlugin.AddCustomFieldMaps(IServiceCollection services)参数类型与typeof(IServiceCollection)进行比较时,该参数不是同一类型。

1 个答案:

答案 0 :(得分:0)

要使其正常运行,请将 PluginLibrary.csproj 更改为此:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.2">
        <Private>false</Private>
        <ExcludeAssets>runtime</ExcludeAssets>
    </PackageReference>
  </ItemGroup>

</Project>

@Ryanman

“他们可能还需要根DI软件包。”

不需要DI根包,因为PluginLibrary项目依赖于Microsoft.Extensions.DependencyInjection.Abstractions中定义的IServiceCollection接口(在CustomFieldsEntityMapBuilderPlugin类中使用)。引用它足以编译项目。通常最好的做法是不包含库的引用,而我们实际上并未使用这些功能。

“您能解释一下为什么包含抽象将有助于解决问题吗?”

包含Abstractions软件包而不是核心DI软件包并不是要解决此问题。这只是一个偏好问题。

解决问题的关键功能是:

<ExcludeAssets>runtime</ExcludeAssets>

我将尝试在下面进行解释:

让我们回到原始的PluginLibrary.csproj代码,并逐步进行调试,以了解幕后情况。首先,我们应该将Program类的第24行和第25行更改为下面的行,以使程序运行时不会引起异常:

methodInfo = t.GetMethod("AddCustomFieldMaps");

在启动程序并加载插件库之后,我们在控制台输出中看到了这一点:

methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection) Returns False
methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection)) Returns False
methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection)) Returns False

这意味着从PluginLibrary项目引用的IServiceCollection类型与 从TestGetMethod项目(当前正在执行的项目)中引用的IServiceCollection类型。

让我们看看文档中有关比较类型的内容。

https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext

“当两个AssemblyLoadContext实例包含相同名称的类型定义时,它们不是同一类型。当且仅当它们来自同一Assembly实例时,它们才是同一类型。”

让我们检查一下我们正在比较的类型的实际加载上下文,并将这些行添加到Main方法的末尾:

Console.WriteLine($"PluginLibrary IServiceCollection load context: {System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(methodInfo.GetParameters()[0].ParameterType.Assembly)}");
Console.WriteLine($"TestGetMethod IServiceCollection load context: {System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(typeof(IServiceCollection).Assembly)}");

启动程序后,我们看到以下内容:

PluginLibrary IServiceCollection load context: "" TestGetMethod.PluginLoadContext #0
TestGetMethod IServiceCollection load context: "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #1

显而易见的是,上下文是不同的,这就是类型比较返回False结果的原因。

现在让我们将PluginLibrary.csproj更改为以下代码,将所有结果文件复制到TestGetMethod输出文件夹并运行程序:

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.2">
    <ExcludeAssets>runtime</ExcludeAssets>
  </PackageReference>
</ItemGroup>

现在,我们在控制台中看到以下内容:

methodInfo.GetParameters()[0].ParameterType == typeof(IServiceCollection) Returns True
methodInfo.GetParameters()[0].ParameterType.Equals(typeof(IServiceCollection)) Returns True
methodInfo.GetParameters()[0].ParameterType.IsAssignableFrom(typeof(IServiceCollection)) Returns True
PluginLibrary IServiceCollection load context: "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #1
TestGetMethod IServiceCollection load context: "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #1

现在该程序可以正常运行,因为它已配置为将Microsoft.Extensions.DependencyInjection程序集及其所有依赖项视为共享。实际上,该配置是在PluginLibrary.deps.json输出文件中定义的。如果不修改PluginLibrary.csproj文件,而是从TestGetMethod输出目录中删除PluginLibrary.deps.json,我们可以达到相同的效果。

相关问题