Base Class Import Resolution

Feb 18, 2009 at 2:54 AM
I have something similar to the following

class ClassA
{
   [Imports()]
   private ISomething something

   // More code
}

[Export()]
class ClassB : ClassA
{
   [Imports()]
   private ISomething something2

   // More code
}

The Import in ClassA is not resolved. The import in ClassB is resolved.

Is this by design? I think it would be very advantageous if Imports in base classes were also resolved.

Yes, I know inheritance hierarchies are a bit out of fashion, but they still solve some problems very well.

Kathleen
Feb 18, 2009 at 3:56 PM
Hi Kathleen

We actually are already fixing this :-). We are making some changes to supporting exports / imports defined on ancestors. This will appear in the next few drops.

Thanks
Glenn

<o:p></o:p>

Feb 18, 2009 at 4:13 PM
Very good. Thank you for the update.

Feb 18, 2009 at 7:27 PM
A different question (which Noopman just asked me about in email) is how can you have an export that is defined on a base class that bubbles up as if you define the export twice you get two different exports. We do allow this today through the usage of our CompositionOptions attribute. Using it you can call tell MEF in your inherited part class "Hey come look at me I have exports!", and it will go surf the child hierarchy to find them.

As an example see the snippet below.

    [Export(typeof(IBar))]
    public class BarBase : IBar { }

    [CompositionOptions(CatalogDiscoverMode = CatalogDiscoverMode.Always)]
    public class Bar : BarBase { }


In this case when we find Bar through our catalog discovery process, we will look into it's children to find exports.

HTH
Glenn



Feb 18, 2009 at 9:34 PM
Cool Glenn thanks!

The reason I found this, for those who want to know, is that the following code produced three instances. Two of the derived class and one of the base class. There were too many ExportAttribute(s).

[TestClass]
public class MEFInheritanceBugTests
{
public interface IBar { }

[Export(typeof(IBar))]
public class BarBase : IBar { }

[Export(typeof(IBar))]
public class Bar : BarBase { }

[Import(typeof(IBar))]
public IEnumerable<IBar> Bars { get; set; }

[TestMethod]
public void ExpectsTwoBarImplementationsButGetsThree()
{
var types = new List<Type>(GetType().GetNestedTypes());
types.Add(GetType());
var catalog = new TypeCatalog(types);
var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);

int expectedBars = 3; // Actually I would have wanted two... The inherited Bar class is produced twice!

Assert.AreEqual(expectedBars, Bars.Count());
}
}

M.

Magnus Mårtensson
Senior Consultant - Scrum Master - MCSD, MCTS
Dotway AB

Tel: +46 (768) 51 00 36

Techie.notepad
Sent from: Malmo Skåne Sweden.
Feb 26, 2009 at 11:21 AM
Edited Feb 26, 2009 at 11:27 AM
+1 from my side. I also came across importing issue with inherited parts.
Any walkarounds for that?
Some details from my side:
Glenn's approach do work (regarding CatalogDiscoverMode.Always) however metadata on the top level type is dropped, the base abstract class metadata is taken only. 
Feb 27, 2009 at 3:15 AM
Denis,

Can you provide a little more information on your scenario? ie Can you show us the hierarchy you have and the behavior you want to see? As Glenn mentioned, we're revisiting this at the moment - and we'd love to see if we missed any scenarios.

Regards

David
Feb 27, 2009 at 7:43 AM
No problem, David. 
Imagine the following situation:
There is some content service that builds Documents (using the factories similar to MefShapes for instance or no matter how)
The core application contains the following class (I've simplified implementation as much as possible just to give you the raw idea )

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/plain")]
[ExportMetadata("Extension", ".txt")]
[CompositionOptions(CreationPolicy = CreationPolicy.NonShared)]
public class TextDocument : IDocument
{
  public virtual string Text { get; set; }
}

application cannot import parts via IDocument interface but by means of global shared contract "Contracts/Document" for example.

Next another developer wants implementing another class that is slighly extending the one above and so that it will get into the ExportCollection of the known documents automatically:

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/xml")]
[ExportMetadata("Extension", ".xml")]
public class XmlDocument : TextDocument
{
}

as a result my ExportCollection on import will get three instances instead of two.There will be 2 TextDocument instances and one XmlDocument.
When marking the XmlDocument part with CatalogDiscoverMode=Never you won't get it imported at all. "Always" mode gives me two instances as far as I remember but both of them contain metadata of the root TextDocument.

If you need some material to play with I can cook a small repro project
Thanks in advance
Developer
Mar 1, 2009 at 1:49 AM
Thanks for the feedback Denis!

You are completely correct that you will get 3 instances, although two of those should result in instances of XmlDocument and one in an instance TextDocument. We completely agree that this scenario is busted and we are trying to figure out the best way to address it.

Just out of curiousity if you had the slightly altered version of your sample:

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/plain")]
[ExportMetadata("Extension", ".txt")]
[CompositionOptions(CreationPolicy = CreationPolicy.NonShared)]
public class TextDocument : IDocument
{
  public virtual string Text { get; set; }
}

[Export("Contracts/XmlDocument")]
[ExportMetadata("ContentType", "text/xml")]
[ExportMetadata("Extension", ".xml")]
public class XmlDocument : TextDocument
{
}

What would you expect in this case? Would you expect two instances of "Contracts/Document" (one of instance type TextDocument and one of instance type XmlDocument) and one instance of "Contracts/XmlDocument" or would only expect one instance of both? To put it another way would you expect XmlDocument to inherit the Exports from it's base type?

Thanks again for the feedback.
Mar 1, 2009 at 10:47 AM
I will try explaining my vision towards the possible scenarios to give you more information to think over.
There might be many IDocument-based types during runtime and only set of them is available within some View. This is why I introduce the "View" contract "Contracts/Document". So TextDocument and XmlDocument are imported once and export collection contains two instances. Extension point is placed to some core static class containing a set of constants (contract names) so that a developer can easily address/use it.

What is my expectation in this case:

1. TextDocument (core)

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/plain")]
[ExportMetadata("Extension", ".txt")]
public class TextDocument : IDocument {}

this is the default document "known" to the core framework of my application. All it knows (for example") is a global contract name "Contracts/Document" and provides a notion of IDocument interface. In this simple scenario Documents are distinguished by content type and extension as a mandatory metadata. Core framework is using interfaces rather than concrete types (that's obvious so won't get into boring details)

2. XmlDocument (external addin)

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/xml")]
[ExportMetadata("Extension", ".xml")]
public class XmlDocument : TextDocument {}

At this very point I should garantee that only one new instance will appear in the document repository, so as a result there will be 1 TextDocument and 1 XmlDocument regardless inheritance tree. This is mandatory because the UI is built on the top of these instances (mainly their metadata) like "File - New" menus, open/save dialogs, etc and that would be a hell doing "distinct" queries on the UI side.

I don't expect the TextDocument to be processed when "parsing" XmlDocument type as the latter overrides every metadata entry of it's parent: "ContentType", "Extension" thus having the same global contract. For the moment it produces two results: one Export for XmlDocument and one Export for it's base type TextDocument so it can be regarded as a mandatory issue for my architure...

However I see another option regarding introducing another IDocument from external side:

3. RtfDocument (external addin)

[Export("Contracts/Document")]
[ExportMetadata("Extension", ".rtf")]
public class RtfDocument : TextDocument {}

Note that I define a third type that is missing [ExportMetadata("ContentType", "text/plain")] attribute declaration. This is because while extending a TextDocument class I would want to inherit the base "text/plain" attribute while other 1 is overriding that of the TextDocument ("Extension" = ".rtf").

So I would say it would be nice inheriting metadata from base classes until the same items are overriden in the final classes. This might bring to another request - dealing with multiple metadata entries:

Multiple metadata entries scenario:

[Export("Contracts/Document")]
[ExportMetadata("ContentType", "text/plain")]
[ExportMetadata("Extension", ".txt", IsMultiple=true)]
[ExportMetadata("Extension", ".rtf", IsMultiple=true)]
public class TextDocument : IDocument {}

this class could be extended in two different ways as far as I see:

1. Adding metatada to the scope of base type

[Export("Contracts/Document")]
[ExportMetadata("Extension", ".dat", IsMultiple=true)]
public class DatDocument : TextDocument {}

the class above could inherit the whole metadata plus add third extension he defines explicitly.

2. Dropping base metadata

[Export("Contracts/Document")]
[ExportMetadata("Extension", ".xml", IsMultiple=false)]
public class XmlDocument : TextDocument {}

the class above could inheric "ContentType" thus replace collection of Extensions with the explicitly defined one.

In all my cases it seems that constructing an export for base type is a major issue however there could be scenarios when it is good. In my situation I would prefer introducing several different Export Contracts to provide different Views of the same data rather than expect MEF do it for me.

Hope this information helps,
Thanks in advance and will answer any follow up questions :)
Aug 18, 2009 at 7:51 AM

Guys, does Preview 6 solves the problems that were discussed within this thread?

Thanks in advance

Aug 18, 2009 at 7:58 AM

It should Dennis.

We now have a new attribute called InheritedExportAttribute which allows you to override the metadata in a deriver without causing a duplicate export, thus one export replaces the other completely.

Glenn

Aug 18, 2009 at 8:28 AM

Thanks, Glen.

I suspected InheritedExportAttribute should come to the rescue in this case.

Have not tested 6th preview thoroughly, just looking through the codebase for the moment and wonder what was the original reason decoupling Export and InheritedExport attributes? From the usability and scalability point of view I would expect using [Export(Inherited=true)] notation rather than a pair of similar attributes. The fact that InheritedExport inherits Export attribute pushes me using InheritedExport all the time when using large object trees with lots of inheritance.

From the first glance on the sources I see that InheritedExportAttribute fetching might give only some minor performance improvements but I don't see any breaking changes if merging these two items into one. However I suspect that I might have not noticed some architecture reason for that.

I'm glad you've unsealed most of the attributes needed for MEF-based frameworks prototyping. I had to change the sources myself in the past and Preview 6 saves my time.

The overview Bicholas Blumhardt gave here: http://blogs.msdn.com/nblumhardt/archive/2009/07/09/mef-preview-6-available.aspx makes Preview 6 more and more appealing to my needs

Thanks,

Denis

Aug 18, 2009 at 9:04 AM
Edited Aug 18, 2009 at 9:05 AM

Denis

Perf was one of the reasons, and it wasn't minor, it was significant and would require us to look at every single member which was very very costly. Furthermore figuring out the overriding for members was next to impossible. We chose for InheritedExport to only be supported on classes and interfaces but not members. It has attribute usage set, and MEF is wired to only look for it in a single place. It doesn't address all scenarios, but does the common set very well, and further it's now pay for play.

Hope this clears things.

Glenn

Aug 18, 2009 at 9:07 AM

Glenn,

That makes sense. I agree that it would be a huge performance degradation if going the generic way.

Thanks for the quick response.

Denis