Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are my types not marked as equal?

There are a few other questions concerning this on SO, but I felt none of them really provided a solid answer.

I'm messing around with reflection a lot lately, and I wanted to check for types contained within a few assemblies that implement a certain interface.

So I have a class called BESCollector that implements ICollector

public class BESCollector : ICollector
{
  // ...
}

Here I load the assembly, loop through all types and see if that type contains an interface of type ICollector...

Assembly pluginAssembly = Assembly.ReflectionOnlyLoadFrom(pluginConfig.AssemblyLocation);
IEnumerable<Type> types = pluginAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICollector)));

... which yields no results :(. While debugging I can clearly see that it DOES contain this type. I break on the 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/");
    }
}

These results are straight from the debugger. Here you can see that interfaces contains a single Type, namely ICollector:

-       interfaces  {System.Type[1]}    System.Type[]
  +     [0] {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"}  System.Type {System.ReflectionOnlyType}

And the Type I'm calling .GetInterfaces() on is clearly BESCollector:

+       type    {Name = "BESCollector" FullName = "SquidReports.DataCollector.Plugin.BES.BESCollector"} System.Type {System.ReflectionOnlyType}

But the equality statement interfaces.Contains(typeof(ICollector)) never evaluates to true.

Just so you think I'm not mixing up types here, when I mouse over (typeof(ICollector)) while debugging, it clearly displays SquidReports.DataCollector.Interface.ICollector.

This, of course, does work:

Type[] interfaces = type.GetInterfaces();
if (interfaces.Any(t => t.Name == typeof(ICollector).Name))
{
    Console.WriteLine(@"\o/");
}

But that does't tell me a whole lot, besides the fact that the types of the same name.

Moreover, why does this check fail?

if (typeof(ICollector).Equals(typeof(ICollector)))
{
    Console.WriteLine("EQUALIZED");
}

Why does my first equality check fail? More specifically, how does type equality work in C# 5.0? Was there nothing specific implemented for .Equals() for the 'Type' type?

EDIT:

At the request of I Quality Catalyst below quickly did a test if the equality would evaluate to true if the interface and class were defined in the same assembly. This DOES work:

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: Here's the implementation of ICollector

public interface ICollector
{
    IDbRelay DbRelay { get; set; }
    ILogManager LogManager { get; set; }

    void Init(ILogManager logManager, IDbRelay dbRelay);
    void Execute();
}

However, I do believe I may have missed out on an important detail. I'm working with three Assemblies here:

  1. The 'Main' (SquidReports.DataCollector) assembly where I'm doing the equality check
  2. The 'Interface' (SquidReports.DataCollector.Interface) assembly that contains ICollector
  3. The 'Plugin' (SquidReports.DataCollector.Plugin.BES) assembly that contains the definition of BESCollector

It is probably very important to note that the 'Interface' (SquidReports.DataCollector.Interface) is being loaded from the ReflectionOnlyAssemblyResolve event, like shown below, because ReflectionOnlyLoadFrom() does not automatically resolves dependencies:

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: At the suggestion of Joe below, I debugged the application again using this:

if (type.Name == "BESCollector")
{
    Type[] interfaces = type.GetInterfaces();
    Type interface1 = interfaces[0];
    Type interface2 = typeof(ICollector);
    Console.WriteLine("");
}

I copied the complete properties of interface1 and interface2, then threw those in a file diff.

The result, I think, may have solved my problem:

interface1 is described as follows:

interface1  {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"}  System.Type {System.ReflectionOnlyType}

Whereas interface2 is described like this:

interface2  {Name = "ICollector" FullName = "SquidReports.DataCollector.Interface.ICollector"}  System.Type {System.RuntimeType}

So am I correct in assuming that the problem is caused because {System.RuntimeType} != {System.ReflectionOnlyType}, and that Assembly.ReflectionOnlyLoadFrom() is to blame for this?

like image 284
romatthe Avatar asked Mar 09 '15 01:03

romatthe


Video Answer


1 Answers

I stumbled upon the answer thanks to Joe Enzminger:

It would appear that, because I was using Assembly.ReflectionOnlyLoadFrom(), the Type that I was loading from the Assembly was actually a System.ReflectionOnlyType as opposed to a System.RuntimeType.

Basically, there is one simple and one pretty cool solution for this:

  1. Stop using ReflectionOnlyLoadFrom() and switch to LoadFrom(). This solves all your problemes. Unless of course you're using ReflectionOnlyLoadFrom for a very good reason.
  2. In that case, you can simply use Type.GUID. Regardless of the context from which the type was loaded, the GUIDs will be the same. So you can simple do

    if ((typeof(MyReflectionType)).GUID == (typeof(MyType)).GUID) { // Blah ... }

That should solve your problems :)

like image 95
romatthe Avatar answered Oct 16 '22 02:10

romatthe