如何将MEF的导入和导出信息持久化到磁盘上

4
对于我在这个问题中描述的应用程序,我想使用MEF来扫描可用的插件程序集,然后将所有可用的导入和导出信息存储在序列化格式中(例如一组字符串或内存流)。这是必要的,因为我需要在应用程序域边界上传输导入和导出信息而不加载插件程序集(本质上,我希望延迟加载插件)。我找到了一些参考资料,例如这篇文章这个,但是这些链接都没有给我任何关于以下事项的想法:
  • 从程序集中提取所有导入和导出
  • 序列化所有所需的导入/导出信息
  • 稍后再将序列化信息重新注入到导入和导出中。
我认为我可以使用ReflectionModelServices类创建Import/Export定义,但是这仍然需要序列化和反序列化部分。有人能够向我指出一些示例、文档或提供建议,告诉我如何执行这些步骤吗?
2个回答

8
这个问题的答案由Kevin在MEF讨论列表上提供。事实证明,可以使用以下代码片段从MEF ExportDefinitionImportDefinition数据结构中提取所有所需信息。
第一步是将程序集类型加载到目录中。然后对于目录中的每个部分,迭代导入和导出定义。导出定义只能放置在类型、方法、属性和字段上(我的代码暂时忽略了它们)。因此,要处理导出,可以使用以下代码。
var exports = new List<Tuple<string, MemberInfo>>();
foreach (var export in part.ExportDefinitions)
{
    var memberInfo = ReflectionModelServices.GetExportingMember(export);
    Tuple<string, MemberInfo> exportDefinition = null;
    switch (memberInfo.MemberType)
    {
        case MemberTypes.Method:
            exportDefinition = new Tuple<string, MemberInfo>(export.ContractName, memberInfo.GetAccessors().First() as MethodInfo);
            break;
        case MemberTypes.NestedType:
        case MemberTypes.TypeInfo:
            exportDefinition = new Tuple<string, MemberInfo>(export.ContractName, memberInfo.GetAccessors().First() as Type);
            break;
        case MemberTypes.Property:
            // this is a bit ugly because we assume that the underlying methods for a property are named as:
            // get_PROPERTYNAME and set_PROPERTYNAME. In this case we assume that exports always
            // have a get method.
            var getMember = memberInfo.GetAccessors().Where(m => m.Name.Contains("get_")).First();
            var name = getMember.Name.Substring("get_".Length);
            var property = getMember.DeclaringType.GetProperty(name);
            exportDefinition = new Tuple<string, MemberInfo>(export.ContractName, property);
            break;

        default:
            throw new NotImplementedException();
    }

    exports.Add(exportDefinition);
}

为了处理只能放置在属性、参数和字段上的导入(再次忽略),可以使用以下代码:
public void ExtractImports()
{
    var imports = new List<Tuple<string, string>>();
    foreach (var import in part.ImportDefinitions)
    {
        SerializedImportDefinition importDefinition = !ReflectionModelServices.IsImportingParameter(import)
            ? importDefinition = CreatePropertyImport(import)
            : importDefinition = CreateConstructorParameterImport(import);
    }
}

private Tuple<string, string> CreatePropertyImport(ImportDefinition import)
{
    var memberInfo = ReflectionModelServices.GetImportingMember(import);
    if (memberInfo.MemberType != MemberTypes.Property)
    {
        throw new ArgumentOutOfRangeException("import");
    }

    // this is a bit ugly because we assume that the underlying methods for a property are named as:
    // get_PROPERTYNAME and set_PROPERTYNAME. In this case we assume that imports always
    // have a set method.
    var getMember = memberInfo.GetAccessors().Where(m => m.Name.Contains("set_")).First();
    var name = getMember.Name.Substring("set_".Length);
    var property = getMember.DeclaringType.GetProperty(name);
    return new Tuple<string, string>(import.ContractName, property.ToString());
}

private Tuple<string, string> CreateConstructorParameterImport(ImportDefinition import)
{
    var parameterInfo = ReflectionModelServices.GetImportingParameter(import);
    return new Tuple<string, string>(import.ContractName, parameterInfo.Value.ToString());
}

请注意,似乎无法通过方法参数提供导入,因此上面的代码不支持这些内容。
一旦所有的导出和导入都被处理完毕,将存储的MemberInfo对象序列化为字符串格式就是一个简单的问题。

1
非常好的工作。我之前一段时间正在寻找类似的东西,正如你可以在我的一些问题中看到的那样,很快我会重新访问这个功能。感谢您的分享和花时间回答自己的问题。 - Adi Lester
memberInfo.GetAccessors() 中的 memberInfo 是从哪里来的? - l33t
@l33t 我已经更新了代码示例,包括获取 memberInfo 对象的部分。希望这样更清楚了。感谢指出缺失的代码行。 - Petrik
谢谢!你的代码没有处理元数据,对吗?只有构造函数和属性中注入的导入。是这样吗? - l33t
@l33t 默认情况下不支持。我不太确定添加这个功能有多难。到目前为止,我还没有需要它。 - Petrik

0

我曾经有同样的需求,最终实现了与@Petrik在他的答案中提到的非常相似的东西。我的LazyAssemblyLoading solution可以在GitHub上找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接