我正在尝试加载包含带有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类型,即使它们来自同一程序集,也可能在不同的上下文中加载。
由于我被要求共享完整的代码,而且我不知道如何上传,所以我现在将其发布在问题中。
<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");
}
}
}
<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)
进行比较时,该参数不是同一类型。
答案 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,我们可以达到相同的效果。