ExportCollection with a list of delegates => internal error in MEF!

May 13, 2009 at 9:13 PM
Edited May 13, 2009 at 9:14 PM

Hi,

I want to import a list of delegates of my own type, Validator. The definitions are exported by static methods. This works fine, using:

[Import]
public IEnumerable<Validator> ValidatorImport
{
    set { _validators = value; }
}

Now each validator method handles one type, so I'd like to put them in a dictionary, keyed by the type they work with. I use the string "ValidatorType" as my metadata key so I can mark each method with the type it handles, e.g.

[Export(typeof(Validator))]
[ExportMetadata("ValidatorType", typeof(int))]
public static ValidationResult ValidateInt(string text)
{
    int value;
    return int.TryParse(text, out value)
                ? new ValidationResult { Value = value }
                : new ValidationResult { Message = "Not a valid integer" };
}

So I change my import to this:

private static IDictionary<Type, Validator> _validators;

[Import]
public static ExportCollection<Validator> ValidatorImport
{
    set
    {
        _validators = value.ToDictionary(
             key => (Type)key.Metadata["ValidatorType"],
             val => val.GetExportedObject());
    }
}

This causes an internal error in MEF in preview 5, in ReflectionProperty.GetValue, at:

Assumes.NotNull(this._getMethod);

I've found I can work around it with an extension method that looks through the delegate to get the metadata attribute:

public static class MefWorkaround
{
    public static object GetMetadata(this Delegate del, string name)
    {
        return del.Method
                .GetCustomAttributes(typeof(ExportMetadataAttribute), true)
                .OfType<ExportMetadataAttribute>()
                .Where(m => m.Name == name)
                .Select(m => m.Value)
                .SingleOrDefault();
    }
}

I use this like so:

[Import]
public IEnumerable<Validator> ValidatorImport
{
    set
    {
        _validators = value.ToDictionary(
            key => (Type)key.GetMetadata("ValidatorType"),
            val => val);
    }
}

But presumably the technique I first tried is supposed to work, or at least not throw an internal error? And how future-proof is this workaround? It may break if MEF inserted some intermediary between each imported delegate and the corresponding exported method.

Developer
May 14, 2009 at 12:02 AM

OK the internal error is caused because you don’t have a get method on the property and it is a custom collection (i.e. ExportCollection), the internal error is fixed in our internal builds but the code you have is still going to cause an error.

 

There are a few things wrong with this import:

[Import]
public static ExportCollection<Validator> ValidatorImport
{
    set
    {
        _validators = value.ToDictionary(
             key => (Type)key.Metadata["ValidatorType"],
             val => val.GetExportedObject());
    }
}

1) MEF does not support Import on static members, this will eventually be caught by an FxCop rule.

2) ExportCollection is a custom collection and so when we satisfy the import we actually try to mutate the collection but you don’t have a get method for this property so we cannot access the value in which causes the error. For custom collections we mutate the collection and we will only call the setter once, if the collection is null and has an empty constructor so we can construct it and set it in the property. Even then it will never have any values in it. If you want a set only property that will have values in it you need to use either an array or IEnumerable (i.e. immutable collections).

3) You should be use ImportMany instead of Import for collection imports. In the current CTP they should work the same but in future CTP’s you will need to use ImportMany so you might as well start now.

 

My suggestion for you is to use the following import:

[ImportMany]
public Export<Validator, IDictionary<string, object>>[] ValidatorImport
{
    set
    {
        _validators = value.ToDictionary(
             key => (Type)key.Metadata["ValidatorType"],
             val => val.GetExportedObject());
    }
}

You should definitely not use the MefWorkaround.GetMetadata because that will definitely break in anything but the simplest cases.

May 14, 2009 at 7:45 AM

Thanks, that's perfect. I figured out the static thing by stepping through the code but I'm not sure I could have guessed the rest!