Getting the type that composition would resolve to

Apr 27, 2009 at 9:09 AM
I want to use MEF to locate components however I need to pass parameters to a constructor - this is part of an API that may or may not be loaded via MEF. As we use MEF already in our application I don't want to have to introduce another technology/approach unless I really have to. 

A few discussions have asked about similar issues and have received a response saying MEF is not designed to do this. That's fine however it would be nice to be able to code round this and it does appear that it is nearly possible ... if GetExport(s) actually told you not just the type that has been defined in the export but also the class that the export had been defined on.

There seems to be something missing with the GetExport calls as you cannot determine which type would be created when everything is composed. So if I define a class that implements an interface for example, calling GetExport(s) does not tell me what it would resolve to - it just tells me the type in the export definition. 

Calling GetExportedObject actually creates an instance of the object and hence can resolve it however I don't want the instance created through a parameterless constructor. Also the values for the constructor could change so I can't use MEF to populate these.

The only way I have found around this is to create a custom attribute that stores the type that has been exported and then use that when I need to create the instance.

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class)]
    public class ActualTypeAttribute : Attribute, IActualType
    {
        public const string ActualTypeMetaData = "ActualType";

        public ActualTypeAttribute(Type realType)
        {
            RealType = realType;
        }
        public Type RealType { get; private set; }
    }

and then use:

    [Export("Service", typeof(DataService))]
    [ActualType(typeof(CachedDataService))]
    public class CachedDataService : DataService
    {
       ...
    }

GetExport(s) does seem to have limited uses unless you can actually find out what the export has been defined on. 

Is this something that will be addressed, and hence allow the above problem to be solved, or is this a deliberate move/limitation of the framework implementation? Or is there a better approach to this.

(Or maybe I have missed something blindingly obvious)

Developer
Apr 28, 2009 at 12:55 AM

We purposely don’t give access to the implementation type because that would defeat loose coupling. The whole idea here is that you don’t take a hard dependency on the implementation and you only work through the contract type with some possible metadata for discrimination if needed.

This is one of the reasons why GetExport(s) does not accept constructor arguments. GetExport(s) is not a factory method it is a contract lookup mechanism. In fact by default if you call GetExport(s) for the same contract multiple times you will get back the same instance.

If an particular implementation has dependencies then that implementation should Import those dependencies (either via ImportingConstructorAttribute or Import/ImportManyAttribute).

Apr 28, 2009 at 8:58 AM
I don't follow how this defeats loose coupling.

Taking as an example ...

If I define two classes that both have the same export definition:

[Export("Service", typeof(DataService))]
public class DataService
{
...
}

[Export("Service", typeof(DataService))]
public class CachedDataService : DataService
{
...
}

When I call GetExports I get two entries in the export collection. This means that the two exports are picked up from the two actual definitions above. As these are actual export definitions I still don't follow why the actual type that it has been defined against cannot be included in the export information. If the export was shared in some way between the two then I could understand this.

Obviously composable parts can be added and removed so I would expect GetExports to potentially return different exports at a later point however they could still point to the instances they were defined on.

I completely understand your comment about it not being a factory method and not accepting parameters to the constructor etc.

At the moment it looks like I will have to continue with my alternative approach.

Developer
Apr 30, 2009 at 2:35 AM
Lets presume you had the type information then on the import side what would you do with that information? It would be very tempting to do the something like the following:

if (export.Metadata.ActualType == typeof(CachedDataService)) { 
     var cached = (CachedDataService)export.GetExportedObject();
    // use some api specific to the CachedDataService
}

In which case you would need to take a static dependency on the assembly with contains CachedDataService. This tightly couples your importing code to the implementation details of that particular DataService export. If there is something that is needed by the importer then it should be provided on the contract type which is DataService in this case. You could also provide extra data for the importer to use in the metadata of an export as well but they shouldn't ever rely on the actual implementation type, only the contract type.
Apr 30, 2009 at 7:26 AM
Edited Apr 30, 2009 at 7:31 AM
In my case I don't want to do anything specific to the type. I just want to call a custom constructor, which can't be part of an interface. To do this I need to know the type to create. So I have no need to do anything specific with the actual type.

For example:

public static T GetInstance<T>(string contract, params object[] parameters)
{
CompositionContainer container = // Create the container etc.
Export<T> export = container.GetExport<T>(contract);
Type type = export.Definition.Metadata["TheActualType"]; // If that was the name of the metadata key for the type that had the export attribute
T result = (T)Activator.CreateInstance(type, parameters);
return result;
}

This knows nothing about the actual type being created - it just needs to know the exported type, DataService. This allows parameters to be passed to the constructor. Obviously this is not great from a safety perspective and it would be better to locate the matching constructor and call that however this gives a simple idea of what could be achieved.

Having said all this I can see why you may not want to expose it if people do use it the way you suggest.

I have implemented a solution which uses the metadata approach and this means I can also define the parameters the constructor expects so I am quite happy with that.

Thanks for taking the time to respond - I appreciate it.