Composing Open Generic Types with Closed Types in MEF 2

1.1k views Asked by At

I understand that starting with MEF 2, MEF supports composing open generic types into closed types. I'm trying to compose a closed type from types exported from two different assemblies added to the same composition container and I'm receiving an ImportCardinalityMismatchException. I'm using conventions for one of the assemblies because it's not under my control. For the other I have used attributes.

I'm not exactly sure how to phrase my question as I find the terminology around generics rather confusing but I'm looking to compose my new closed type without explicitly implementing my own class, inheriting from Foo, and supplying it my FooUser type parameter. I don't know if this is an issue with how I'm doing this or if it has something to do with the fact that the types are in different assemblies.

In one assembly I have the following:

public class Foo<T> where T : Bar {}
public class Bar {}

In another assembly I have the following:

[Export]
public class Bar2 : Bar {}

[Export]
public class Something
{
    [ImportingConstructor] 
    public Something([Import(typeof(Foo<>))] Foo<Bar2> foo) {}
}

In my registration code I have done the following:

var conventions = new RegistrationBuilder();
conventions.ForType(typeof(Foo<>)).Export();

var aggregateCatalog = new AggregateCatalog();
var catalog = new AssemblyCatalog(typeof(Foo<>).Assembly, conventions);
aggregateCatalog.Catalogs.Add(catalog);

catalog = new AssemblyCatalog(typeof(Something).Assembly);
aggregateCatalog.Catalogs.Add(catalog);

catalog = new AssemblyCatalog(typeof(Bar2).Assembly);
aggregateCatalog.Catalogs.Add(catalog);

var container = new CompositionContainer(aggregateCatalog, CompositionOptions.DisableSilentRejection);
var batch = new CompositionBatch();
batch.AddExportedValue(container);
container.Compose(batch);

Later I try to export my value thusly:

container.GetExportedValue<Something>();

Exception:Thrown: "No exports were found that match the constraint: ContractName Foo(Bar2) RequiredTypeIdentity Foo(Bar2)" (System.ComponentModel.Composition.ImportCardinalityMismatchException) A System.ComponentModel.Composition.ImportCardinalityMismatchException was thrown: "No exports were found that match the constraint: ContractName Foo(Bar2) RequiredTypeIdentity Foo(Bar2)"

I've looked in my conventions instance and in the container I have my parts i.e. Foo{0}, Bar2, and Something. I still receive the System.ComponentModel.Composition.ImportCardinalityMismatchException however.

I have seen this done in more abstract cases, say where one has IRepository but not where one has something more concrete nor for items spanning assemblies. Any assistance would be greatly appreciated. Barring anything helpful, I'll probably just inherit from the offending types and be done with it.

Edit: I just built the very simplified example detailed above on the off chance that I am actually doing something different in my real world project than I am here and I have vary similar results. I've renamed a few types to bring them in line with my simplified example.

The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) No exports were found that match the constraint: ContractName CompositionTestLibrary.Foo(CompositionTestLibrary2.Bar2) RequiredTypeIdentity CompositionTestLibrary.Foo(CompositionTestLibrary2.Bar2)

Resulting in: Cannot set import 'CompositionTest.Something..ctor (Parameter="foo", ContractName="CompositionTestLibrary.Foo(CompositionTestLibrary2.Bar2)")' on part 'CompositionTest.Something'. Element: CompositionTest.Something..ctor (Parameter="foo", ContractName="CompositionTestLibrary.Foo(CompositionTestLibrary2.Bar2)") --> CompositionTest.Something --> AssemblyCatalog (Assembly="CompositionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")

Resulting in: Cannot get export 'CompositionTest.Something (ContractName="CompositionTest.Something")' from part 'CompositionTest.Something'. Element: CompositionTest.Something (ContractName="CompositionTest.Something") --> CompositionTest.Something --> AssemblyCatalog (Assembly="CompositionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")

2

There are 2 answers

1
Adi Lester On

In the following line you should not use the conventions variable, so you should change

catalog = new AssemblyCatalog(typeof(FooUser).Assembly, conventions);

to

catalog = new AssemblyCatalog(typeof(FooUser).Assembly);

Using conventions here will effectively not export anything from the assembly where FooUser and Something are defined, so you won't be able to get a composed value of Something. Removing it will will allow Something to be exported and composed.

1
binki On

Passing conventions to a catalog prevents MEF from closing generic types with type constraints. Consider these classes:

[Export]
public class Foo<T> where T : Bar { }
[Export]
public class FooUnconstrained<T> { }
[Export]
public class Bar { }

Closing an unconstrained generic type works regardless of whether RegistrationBuilder is passed:

using (var catalogue = new ApplicationCatalog(new RegistrationBuilder()))
using (var container = new CompositionContainer(catalogue))
{
    Console.WriteLine(container.GetExport<FooUnconstrained<Bar>>().Value);
}

Closing a constrained generic type only works without RegistrationBuilder:

using (var catalogue = new ApplicationCatalog())
using (var container = new CompositionContainer(catalogue))
{
    Console.WriteLine(container.GetExport<Foo<Bar>>().Value);
}

Closing a constrained generic type fails when using RegistrationBuilder:

using (var catalogue = new ApplicationCatalog(new RegistrationBuilder()))
using (var container = new CompositionContainer(catalogue))
{
    // Next line throws ImportCardinalityMismatchException:
    Console.WriteLine(container.GetExport<Foo<Bar>>().Value);
}

This seems like a bug with .net. I am experiencing this behavior on releaseKey=528040 (.net-4.8 preview).

I personally recommend against the conventional model anyway because it requires the compositor to know about all of the conventions. This means that you cannot bring together two different code bases which use different conventions without manually combining their convention logic. I.e., it introduces tight coupling that doesn’t exist with the attributed model.