views:

59

answers:

1

I've got a circular dependency that recently came about because of a change in my application architecture.

The application relies on a plugin manager that loads plugins via MEF. Everything up until worked fine, because it looked something like this:

// model.cs
[Export("Model")]
public class Model
{
  public PluginManager PM { get; set; }

  [ImportingConstructor]
  public Model( [Import] PluginManager plugin_manager)
  {
    PM = plugin_manager;
  }
}

// pluginmanager.cs
[Export(typeof(PluginManager))]
public class PluginManager
{
  [ImportMany(typeof(PluginInterface))]
  private IEnumerable<PluginInterface> Plugins { get; set; }
}

and the plugins looked like this:

// myplugin.cs
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
}

But now I've got a situation where I want all plugins to have the ability to query the PluginManager (or possibly any other object) via an interface to find out about other plugins in the system to find out about their capabilities. I "solved" this by adding another interface, let's call it PluginQueryInterface. I then had the Model implement this interface.

[Export("Model"))]
[Export(typeof(PluginQueryInterface))]
public class Model : PluginQueryInterface
{
  // same as before
}

and then the plugin signature would look like this:

// 1st possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
  [Import(typeof(PluginQueryInterface))]
  public PluginQueryInterface QueryInterface { get; set; }

  public MyPlugin() {}
}

or this

// 2nd possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
  private  PluginQueryInterface QueryInterface { get; set; }

  [ImportingConstructor]
  public MyPlugin( [Import] PluginQueryInterface query_interface)
  {
    QueryInterface = query_interface
  }
}

The 2nd implementation is pretty clearly a circular reference, because the plugins requires that the PluginQueryInterface be created before the plugin is created, but the PluginQueryInterface is the Model, which must import the PluginManager, which in turn needs all of the PluginInterfaces created... and I do get a MEF circular dependency error when I launch.

The 1st implementation doesn't seem like a circular reference to me. If the PluginQueryInterface is a property, then I thought it wouldn't be resolved until it was used. And it isn't used by the constructor at all. So why wouldn't the PluginManager merrily create all of my MyPlugins? I get the same MEF error in both cases.

I have tried to solve this problem by then making the PluginManager implement the PluginQueryInterface, because a) it makes sense anyway and b) it's a known way of dealing with circular dependencies -- make the two interdependent classes instead depend on a third class. Now the problem is that I get a different MEF error! This is what it says:

GetExportedValue cannot be called before prerequisite import 'Company.App.PluginManager..ctor(Parameter="database_filepath", ContractName="PluginManager.filename")' has been set.

WTF? I set breakpoints in my code, and my exported value PluginManager.filename has been set before calling GetExportedValue.

I am totally stumped. Any observations or suggestions would be greatly appreciated right now. I've been banging my head against the MEF-clad wall for hours trying to debug this problem.

(updated)

I didn't think about this earlier, but it could have been differences between plugins, so I deleted one of the two plugins, and now my application loads without MEF errors. I added it back, and it failed again. Then I deleted the other plugin, and it worked. So it looks like this is some other MEF error. It's almost as if it doesn't want me to load more than one plugin with a specific interface... but I am using ImportMany, and wouldn't that have manifested itself as a CardinalityException of some kind?

UPDATE

I don't understand this part of MEF, and hopefully someone here can explain what it's all about. After stepping into the code for some time, I found that my error stemmed from MEF removing import definitions after finding the value!

    private bool TryGetImportValue(ImportDefinition definition, out object value)
    {
        lock (this._lock)
        {
            if (this._importValues.TryGetValue(definition, out value))
            {
                this._importValues.Remove(definition); // this is the line that got me
                return true;
            }
        }

        value = null;
        return false;
    }

I've never had this problem before, and frankly I'm having a hard time understanding what I'm doing now with my imports and exports that has made this problem surface. I assume that I'm doing something that the MEF designers hadn't intended anyone to do. I could blindly comment out the this._importValues.Remove(definition);, but that couldn't possibly be right. My guess is that this will boil down to the MEF attributes I've used, but since the plugin that imports this value has a creation policy of CreationPolicy.Shared, why would I have a problem?

A: 

Well, I've got a possible solution. I don't have any experience with using this, but using Lazy instantiation seems to help. At least I can move forward without having to change MEF code that I don't completely understand.

Dave