为什么我的类型没有标记为相等?
Why are my types not marked as equal?
SO 上还有其他一些与此相关的问题,但我觉得 none 确实提供了可靠的答案。
最近我经常使用反射,我想检查一些实现特定接口的程序集中包含的类型。
所以我有一个名为 BESCollector 的 class 实现了 ICollector
public class BESCollector : ICollector
{
// ...
}
我在这里加载程序集,遍历所有类型并查看该类型是否包含 ICollector 类型的接口...
Assembly pluginAssembly = Assembly.ReflectionOnlyLoadFrom(pluginConfig.AssemblyLocation);
IEnumerable<Type> types = pluginAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICollector)));
... 没有结果:(。在调试时我可以清楚地看到它确实包含这种类型。我打破了 if-loop
Assembly pluginAssembly = Assembly.ReflectionOnlyLoadFrom(pluginConfig.AssemblyLocation);
IEnumerable<Type> types = pluginAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICollector)));
foreach (Type type in pluginAssembly.GetTypes())
{
Type[] interfaces = type.GetInterfaces();
if (interfaces.Contains(typeof(ICollector)))
{
Console.WriteLine(@"\o/");
}
}
这些结果直接来自调试器。在这里你可以看到 interfaces
包含一个类型,即 ICollector:
- interfaces {System.Type[1]} System.Type[]
+ [0] {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.ReflectionOnlyType}
我调用 .GetInterfaces()
的类型显然是 BESCollector:
+ type {Name = "BESCollector" FullName = "SquidReports.DataCollector.Plugin.BES.BESCollector"} System.Type {System.ReflectionOnlyType}
但是等式语句 interfaces.Contains(typeof(ICollector))
永远不会计算为真。
只是想让你认为我没有混淆类型,当我在调试时将鼠标悬停在 (typeof(ICollector))
上时,它清楚地显示 SquidReports.DataCollector.Interface.ICollector
.
这当然有效:
Type[] interfaces = type.GetInterfaces();
if (interfaces.Any(t => t.Name == typeof(ICollector).Name))
{
Console.WriteLine(@"\o/");
}
但这并不能告诉我很多,除了同名的类型。
此外,为什么这个检查会失败?
if (typeof(ICollector).Equals(typeof(ICollector)))
{
Console.WriteLine("EQUALIZED");
}
为什么我的第一次相等性检查失败了?更具体地说,C# 5.0 中的类型相等性是如何工作的? 'Type' 类型的 .Equals() 没有具体实施吗?
编辑:
应 I Quality Catalyst 的要求,下面的 Quality Catalyst 快速测试了如果接口和 class 在同一个程序集中定义,相等性是否会评估为真。这确实有效:
class Program
{
public interface ITest
{
}
public class Test : ITest
{
public int ID { get; set; }
}
static void Main(string[] args)
{
Type[] interfaces = (typeof(Test)).GetInterfaces();
if (interfaces.Any(t => t == typeof(ITest)))
{
Console.WriteLine(@"\o/");
}
}
}
EDIT2:这是 ICollector
的实现
public interface ICollector
{
IDbRelay DbRelay { get; set; }
ILogManager LogManager { get; set; }
void Init(ILogManager logManager, IDbRelay dbRelay);
void Execute();
}
但是,我相信我可能错过了一个重要的细节。我在这里使用三个程序集:
- 我正在执行相等性检查的 'Main' (
SquidReports.DataCollector
) 程序集
- 包含 ICollector 的 'Interface' (
SquidReports.DataCollector.Interface
) 程序集
- 包含 BESCollector 定义的 'Plugin' (
SquidReports.DataCollector.Plugin.BES
) 程序集
注意 'Interface' (SquidReports.DataCollector.Interface
) 是从 ReflectionOnlyAssemblyResolve 事件加载的可能非常重要,如下所示,因为 ReflectionOnlyLoadFrom() 不会自动解析依赖项:
public Assembly ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
// This event is used to resolve dependency problems. We need to return the requested assembly in this method.
// We should probably look in two locations:
// 1. The SquidReports.DataCollector folder
// 2. The Corresponding folder in SquidReports.DataCollector\Plugins
// Let's first turn the 'full' assembly name into something more compact
AssemblyName assemblyName = new AssemblyName(args.Name);
this.Logger.LogMessage(LogLevel.Debug, String.Format("Attempting to resolve Assembly {0} to load {0}", assemblyName.Name, args.RequestingAssembly.GetName().Name));
// Let's also get the location where the requesting assembly is located
DirectoryInfo pluginDirectory = Directory.GetParent(args.RequestingAssembly.Location);
string assemblyFileName = String.Format("{0}.dll", assemblyName.Name);
if (File.Exists(assemblyFileName))
{
// It's in the main bin folder, let's try to load from here
return Assembly.ReflectionOnlyLoadFrom(assemblyFileName);
}
else if (File.Exists(Path.Combine(pluginDirectory.FullName, assemblyFileName)))
{
// It's in the plugin folder, let's load from there
return Assembly.ReflectionOnlyLoadFrom(Path.Combine(pluginDirectory.FullName, assemblyFileName));
}
return null;
}
EDIT3:根据下面 Joe 的建议,我再次调试了应用程序:
if (type.Name == "BESCollector")
{
Type[] interfaces = type.GetInterfaces();
Type interface1 = interfaces[0];
Type interface2 = typeof(ICollector);
Console.WriteLine("");
}
我复制了 interface1
和 interface2
的完整属性,然后将它们放入 diff 文件中。
结果,我想,可能解决了我的问题:
interface1
说明如下:
interface1 {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.ReflectionOnlyType}
而 interface2
是这样描述的:
interface2 {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.RuntimeType}
所以我假设问题是由于 {System.RuntimeType} != {System.ReflectionOnlyType} 造成的,并且 Assembly.ReflectionOnlyLoadFrom() 是造成这个的原因是正确的吗?
感谢 Joe Enzminger,我偶然发现了答案:
看来,因为我使用的是 Assembly.ReflectionOnlyLoadFrom()
,所以我从程序集加载的类型实际上是 System.ReflectionOnlyType,而不是 System.RuntimeType。
基本上,有一种简单且非常酷的解决方案:
- 停止使用
ReflectionOnlyLoadFrom()
并切换到 LoadFrom()。这解决了你所有的问题。当然,除非您出于充分的理由使用 ReflectionOnlyLoadFrom。
在这种情况下,您可以简单地使用Type.GUID。无论从中加载类型的上下文如何,GUID 都将相同。所以你可以简单地做
if ((typeof(MyReflectionType)).GUID == (typeof(MyType)).GUID)
{
// 废话...
}
这应该可以解决您的问题:)
SO 上还有其他一些与此相关的问题,但我觉得 none 确实提供了可靠的答案。
最近我经常使用反射,我想检查一些实现特定接口的程序集中包含的类型。
所以我有一个名为 BESCollector 的 class 实现了 ICollector
public class BESCollector : ICollector
{
// ...
}
我在这里加载程序集,遍历所有类型并查看该类型是否包含 ICollector 类型的接口...
Assembly pluginAssembly = Assembly.ReflectionOnlyLoadFrom(pluginConfig.AssemblyLocation);
IEnumerable<Type> types = pluginAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICollector)));
... 没有结果:(。在调试时我可以清楚地看到它确实包含这种类型。我打破了 if-loop
Assembly pluginAssembly = Assembly.ReflectionOnlyLoadFrom(pluginConfig.AssemblyLocation);
IEnumerable<Type> types = pluginAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICollector)));
foreach (Type type in pluginAssembly.GetTypes())
{
Type[] interfaces = type.GetInterfaces();
if (interfaces.Contains(typeof(ICollector)))
{
Console.WriteLine(@"\o/");
}
}
这些结果直接来自调试器。在这里你可以看到 interfaces
包含一个类型,即 ICollector:
- interfaces {System.Type[1]} System.Type[]
+ [0] {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.ReflectionOnlyType}
我调用 .GetInterfaces()
的类型显然是 BESCollector:
+ type {Name = "BESCollector" FullName = "SquidReports.DataCollector.Plugin.BES.BESCollector"} System.Type {System.ReflectionOnlyType}
但是等式语句 interfaces.Contains(typeof(ICollector))
永远不会计算为真。
只是想让你认为我没有混淆类型,当我在调试时将鼠标悬停在 (typeof(ICollector))
上时,它清楚地显示 SquidReports.DataCollector.Interface.ICollector
.
这当然有效:
Type[] interfaces = type.GetInterfaces();
if (interfaces.Any(t => t.Name == typeof(ICollector).Name))
{
Console.WriteLine(@"\o/");
}
但这并不能告诉我很多,除了同名的类型。
此外,为什么这个检查会失败?
if (typeof(ICollector).Equals(typeof(ICollector)))
{
Console.WriteLine("EQUALIZED");
}
为什么我的第一次相等性检查失败了?更具体地说,C# 5.0 中的类型相等性是如何工作的? 'Type' 类型的 .Equals() 没有具体实施吗?
编辑:
应 I Quality Catalyst 的要求,下面的 Quality Catalyst 快速测试了如果接口和 class 在同一个程序集中定义,相等性是否会评估为真。这确实有效:
class Program
{
public interface ITest
{
}
public class Test : ITest
{
public int ID { get; set; }
}
static void Main(string[] args)
{
Type[] interfaces = (typeof(Test)).GetInterfaces();
if (interfaces.Any(t => t == typeof(ITest)))
{
Console.WriteLine(@"\o/");
}
}
}
EDIT2:这是 ICollector
的实现public interface ICollector
{
IDbRelay DbRelay { get; set; }
ILogManager LogManager { get; set; }
void Init(ILogManager logManager, IDbRelay dbRelay);
void Execute();
}
但是,我相信我可能错过了一个重要的细节。我在这里使用三个程序集:
- 我正在执行相等性检查的 'Main' (
SquidReports.DataCollector
) 程序集 - 包含 ICollector 的 'Interface' (
SquidReports.DataCollector.Interface
) 程序集 - 包含 BESCollector 定义的 'Plugin' (
SquidReports.DataCollector.Plugin.BES
) 程序集
注意 'Interface' (SquidReports.DataCollector.Interface
) 是从 ReflectionOnlyAssemblyResolve 事件加载的可能非常重要,如下所示,因为 ReflectionOnlyLoadFrom() 不会自动解析依赖项:
public Assembly ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
// This event is used to resolve dependency problems. We need to return the requested assembly in this method.
// We should probably look in two locations:
// 1. The SquidReports.DataCollector folder
// 2. The Corresponding folder in SquidReports.DataCollector\Plugins
// Let's first turn the 'full' assembly name into something more compact
AssemblyName assemblyName = new AssemblyName(args.Name);
this.Logger.LogMessage(LogLevel.Debug, String.Format("Attempting to resolve Assembly {0} to load {0}", assemblyName.Name, args.RequestingAssembly.GetName().Name));
// Let's also get the location where the requesting assembly is located
DirectoryInfo pluginDirectory = Directory.GetParent(args.RequestingAssembly.Location);
string assemblyFileName = String.Format("{0}.dll", assemblyName.Name);
if (File.Exists(assemblyFileName))
{
// It's in the main bin folder, let's try to load from here
return Assembly.ReflectionOnlyLoadFrom(assemblyFileName);
}
else if (File.Exists(Path.Combine(pluginDirectory.FullName, assemblyFileName)))
{
// It's in the plugin folder, let's load from there
return Assembly.ReflectionOnlyLoadFrom(Path.Combine(pluginDirectory.FullName, assemblyFileName));
}
return null;
}
EDIT3:根据下面 Joe 的建议,我再次调试了应用程序:
if (type.Name == "BESCollector")
{
Type[] interfaces = type.GetInterfaces();
Type interface1 = interfaces[0];
Type interface2 = typeof(ICollector);
Console.WriteLine("");
}
我复制了 interface1
和 interface2
的完整属性,然后将它们放入 diff 文件中。
结果,我想,可能解决了我的问题:
interface1
说明如下:
interface1 {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.ReflectionOnlyType}
而 interface2
是这样描述的:
interface2 {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"} System.Type {System.RuntimeType}
所以我假设问题是由于 {System.RuntimeType} != {System.ReflectionOnlyType} 造成的,并且 Assembly.ReflectionOnlyLoadFrom() 是造成这个的原因是正确的吗?
感谢 Joe Enzminger,我偶然发现了答案:
看来,因为我使用的是 Assembly.ReflectionOnlyLoadFrom()
,所以我从程序集加载的类型实际上是 System.ReflectionOnlyType,而不是 System.RuntimeType。
基本上,有一种简单且非常酷的解决方案:
- 停止使用
ReflectionOnlyLoadFrom()
并切换到 LoadFrom()。这解决了你所有的问题。当然,除非您出于充分的理由使用 ReflectionOnlyLoadFrom。 在这种情况下,您可以简单地使用Type.GUID。无论从中加载类型的上下文如何,GUID 都将相同。所以你可以简单地做
if ((typeof(MyReflectionType)).GUID == (typeof(MyType)).GUID) { // 废话... }
这应该可以解决您的问题:)