类型约束的开放式泛型与RegistrationBuilder不兼容

5
当使用RegistrationBuilder时,下面的代码不起作用。当未将RegistrationBuilder添加到AssemblyCatalog构造函数中时,类型约束泛型可正常工作。
[TestClass]
public class TypeConstraints
{
    [TestMethod]
    public void TypeConstraintTest()
    {
        var rb = new RegistrationBuilder();
        var a = new AssemblyCatalog(Assembly.GetExecutingAssembly(), rb);
        //var a = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //Works!
        var aggr = new AggregateCatalog(a);
        var c = new CompositionContainer(aggr);
        var item = c.GetExportedValue<IConstrained<Item>>();
        Assert.IsNotNull(item);
    }
}

public interface IConstrained<T> where T : IItem
{}

[Export(typeof (IConstrained<>))]
public class Constrained<T> : IConstrained<T> where T : IItem
{}

public class Item : IItem
{}

public interface IItem
{}

请详细说明,var rb = new RegistrationBuilder(); var a = new AssemblyCatalog(Assembly.GetExecutingAssembly(), rb); rb未使用,不做任何操作。 - ale
它目前什么也不做,但它改变了行为,也就是打开了泛型。计划是在基本情况下通过简单传递来使用它。 - Roman Dvoskin
2个回答

3

首先让我们描述一下导致这种行为的原因。

RegistrationBuilder将程序集的实际类型封装在一个称为CustomType的代理类型中。这个代理几乎只存在于为RegistrationBuilder提供机会在运行时注入Export和Import属性。

不幸的是,当您调用GetGenericParameterConstraints时,此代理还会返回包装的类型。所以你得到的不是RuntimeType IItem而是CustomType IItem。当您尝试获取IConstrained的导出时,AssemblyCatalog会检查很多东西,以确定您的导出是否与导入匹配。其中一个检查是泛型类型约束是否满足。它基本上是这样的一个检查。(简化)

exportToCheck.GenericTypeConstraints[0].IsAssignableFrom(typeof(Item))

CustomType的IsAssignableForm方法实现如下。
public override bool IsAssignableFrom(Type c)
{
    ProjectingType projectingType = c as ProjectingType;
    return !(projectingType == null) && this.Projector == projectingType.Projector && 
              base.UnderlyingType.IsAssignableFrom(projectingType.UnderlyingType);
}

只有在传递另一种代理类型时,它才起作用。

我真的认为这是 RegistrationBuilder 的一个重大错误,你应该向 Microsoft Connect 报告它。

要解决此问题,你需要取消投影与 ComposablePartDefinition 保存的 GenericTypeContraints。

坏消息是所有相关类都是内部类,因此你无法仅覆盖 GetGenericParameterConstraints 方法。

我通过继承 AssemblyCatalog 并手动取消投影约束类型来解决了这个问题。

public class MyAssemblyCatalog : AssemblyCatalog { private Func unprojectDelegate;

private bool projectionsChecked = false;

public MyAssemblyCatalog(Assembly assembly, CustomReflectionContext reflectionContext)
    : base(assembly, reflectionContext)
{
    this.ReflectionContext = reflectionContext;
}

public CustomReflectionContext ReflectionContext { get; private set; }

public Type Unproject(Type type)
{
    if (this.unprojectDelegate == null) {
        var param = Expression.Parameter(typeof(CustomReflectionContext));
        var param2 = Expression.Parameter(typeof(Type));
        var prop = Expression.Property(param, param.Type.GetProperty("Projector", BindingFlags.Instance | BindingFlags.NonPublic));
        var method = prop.Type.GetMethod("Unproject", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(Type) }, null);
        var body = Expression.Call(prop, method, param2);
        this.unprojectDelegate = Expression.Lambda<Func<CustomReflectionContext, Type, Type>>(body, param, param2).Compile();
    }
    return unprojectDelegate(this.ReflectionContext, type);
}

private void EnsureUnprojectedGenericTypeConstraints()
{
    if (!this.projectionsChecked) {
        foreach (var item in this) {
            object value1;
            if (item.Metadata.TryGetValue("System.ComponentModel.Composition.GenericParameterConstraints", out value1)) {
                var items = (object[])value1;
                foreach (var entry in items) {
                    var types = entry as Type[];
                    if (types != null) {
                        for (int i = 0; i < types.Length; i++) {
                            types[i] = Unproject(types[i]);
                        }
                    }
                }
            }
        }
        projectionsChecked = true;
    }
}

public override System.Collections.Generic.IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
{
    EnsureUnprojectedGenericTypeConstraints();
    return base.GetExports(definition);
}

现在测试已经能够工作。

[TestMethod]
public void TypeConstraintTest()
{
    var rb = new RegistrationBuilder();

    var a = new MyAssemblyCatalog(Assembly.GetExecutingAssembly(), rb);

    var aggr = new AggregateCatalog(a);
    var c = new CompositionContainer(aggr);
    var item = c.GetExportedValue<IConstrained<Item>>();

    Assert.IsNotNull(item);
}

感谢您的深入研究。出于好奇,您是如何查看MEF源代码的?是通过ILSpy反编译还是找到了实际的源代码? - Roman Dvoskin
1
我向微软提交了一个错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/916668/type-constrained-open-generics-do-not-work-when-using-assemblycatalog-with-registrationbuilder# - Roman Dvoskin
当涉及“GetExports”调用Lazy<T,TMetadata>的.Value(但仅在列表的最后一个上),以及GetExportedValues时,此解决方案存在问题。 在获取ExportedValues时会出现NotSupported异常。 在System.Reflection.Emit.TypeBuilderInstantiation.IsAssignableFrom(Type c) 在System.ComponentModel.Composition.ReflectionModel.GenericServices.CanSpecialize(Type type, IEnumerable1 constraintTypes)。` - David Martin

0
更简单的解决方案:
/// <summary>
/// When RegistrationBuilder is used, there is problem with Generics constraints - in produced ExportDefinition is generics constraint with descriptior CustomType which is not comparable with Type. 
/// * so composition failed on Export not found exception.
/// https://dev59.com/_oHba4cB1Zd3GeqPO0ET
/// </summary>
public static class PatchCatalogForRegistrationBuilderBug
{
    public static void FixCatalogForRegistrationBuilderBug(this ComposablePartCatalog catalog)
    {
        foreach (var item in catalog)
        {
            object value1;
            if (item.Metadata.TryGetValue("System.ComponentModel.Composition.GenericParameterConstraints", out value1))
            {
                var items = (object[])value1;
                foreach (var entry in items)
                {
                    var types = entry as Type[];
                    if (types != null)
                    {
                        for (int i = 0; i < types.Length; i++)
                        {
                            if (((object)types[i]).GetType().FullName != "System.Reflection.Context.Custom.CustomType") continue; //cast to object is only for due to R# warning
                            types[i] = types[i].UnderlyingSystemType;
                        }
                    }
                }
            }
        }
    }
}

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