.NET 4.5 CustomReflectionContext:它有用吗?

时间:2012-02-29 21:54:07

标签: .net reflection base-class-library .net-4.5

What's New in the .NET Framework 4.5 Developer Preview提及

  

能够自定义反射上下文以覆盖默认值   通过CustomReflectionContext类的反射行为。

ReflectionContext的目的是什么? MSDN在这个问题上并不十分清楚。

1 个答案:

答案 0 :(得分:39)

过去使用.NET时,希望能够通过反射自动化某些功能并能够自定义它们之间存在紧张关系。例如,在Visual Studio中使用“属性”面板 - 在显示某种.NET类型(例如,设计图面上的控件)的情况下,它可以自动发现并显示该类型定义的每个公共属性。

对这种类型驱动的行为使用反射非常有用,因为这意味着每个属性都会显示,而控件的开发人员无需执行任何操作。但它提出了一个问题:如果你想定制东西,例如为特定属性定义分类或自定义编辑UI?

.NET中的经典解决方案是将一堆自定义属性打到相关成员上。但是,其中一个问题是它可能意味着代码中的部分代码在运行时最终会执行有意义的工作,这取决于在设计时只执行任何操作的类 - 依赖于属性会阻止您分离运行时和设计时间方面。您是否真的想为VS的属性面板提供自定义设计器UI的代码,作为最终用户计算机上的控件库的一部分?

另一个问题是,在某些情况下,您可能想要动态决定哪些属性'你出席。其中一个最古老的例子(可追溯到.NET 1.0)是将DataSet置于某种网格控件(客户端或Web)中。对于强类型数据集,反射可能是网格发现源提供哪些属性的合适方式,但DataSet也可以动态使用,因此您需要一种方法让数据网格在运行时询问什么要显示的列。

(答案是:正确设计你的用户界面!直接生成这样的网格会导致糟糕的用户体验。但是,很多人都想以懒惰的方式去做,不管它是不是一个好主意...)

因此,您有时会想要反射驱动的行为,但有时您希望能够在运行时进行全面控制。

为此出现了各种临时解决方案。您拥有整个TypeDescriptorPropertyDescriptor系列类型,它们在反射之上提供了一种可虚拟化的视图。默认情况下,这只会直接从反射中传递所有内容,但类型有机会选择在运行时提供自定义描述符,使他们能够修改甚至完全替换它们的外观。 ICustomTypeDescriptor是该世界的一部分。

这提供了一个解决方案,默认情况下需要反射驱动的行为,如果需要,可以选择提供运行时驱动的行为。但它并没有解决您只想在设计时执行此操作的问题,并且您不希望必须将该代码作为运行时可再发行组件的一部分发布。

几年前,Visual Studio引入了自己的临时机制,用于在设计时增加类型信息。这是一系列约定驱动的行为,Visual Studio将自动发现与特定运行时组件相关的设计时组件,使您能够自定义设计体验,而无需将相关代码烘焙到可再发行组件中。 Blend也使用这种机制,虽然有一些调整,可以为VS和Blend提供不同的设计师作品。

当然,通过普通反射API看不到这一点 - VS和Blend有一个位于反射顶部的包装层,使这一切都能正常工作。

所以现在我们有两个可以通过反思的虚拟化层,或者可以增加反射出来的内容......

看起来在.NET 4.5中,CLR团队决定由于各个团队已经在做这类事情,而其他团队想要做更多的事情(MEF团队对反射驱动有类似要求 - 可选运行时增强行为),这正是应该在运行时中构建的那种东西。

新模型似乎是这样的:ReflectionContext基类是一个抽象API,通过它您可以获得反射API的虚拟化版本。它' S看似简单,因为主要思路之一是,你不再需要专门的API,如类型描述系统,如果你唯一的目标是获得一个虚拟化的包装上反射的顶部 - 反射现在虚拟化的开箱即用。所以你可以写这种东西

public static void ShowAllAttributes(Type t)
{
    foreach (Attribute attr in t.GetCustomAttributes(true))
    {
        Console.WriteLine(attr);
    }
}

现在你总是能够写出来,但在.NET 4.5之前,像这样的代码总是会反对真实的'输入信息,因为它使用反射。但是由于反射背景,现在可以为其提供虚拟化Type。所以考虑这个非常无聊的类型:

class NoRealAttributes
{
}

如果您只是将typeof(NoRealAttributes)传递给我的ShowAllAttributes方法,它将不会打印出任何内容。但我可以写一个(有点人为的)自定义反思上下文:

class MyReflectionContext : CustomReflectionContext
{
    protected override IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes)
    {
        if (member == typeof(NoRealAttributes))
        {
            return new[] { new DefaultMemberAttribute("Foo") };
        }
        else
        {
            return base.GetCustomAttributes(member, declaredAttributes);
        }
    }
}

(顺便说一下,我认为CustomReflectionContext和它的基础ReflectionContext之间的区别在于后者定义了可虚拟化反射上下文的API,而CustomReflectionContext添加了一些帮助器让你更容易实现这样的事情。)现在我可以使用它来为我的班级提供Type的虚拟化版本:

var ctx = new MyReflectionContext();
Type mapped = ctx.MapType(typeof(NoRealAttributes).GetTypeInfo());
ShowAllAttributes(mapped);

在此代码中,mapped仍然引用Type对象,因此任何知道如何使用反射API的内容都能够使用它,但现在它将报告是否存在实际上并不存在的属性。当然,Type是抽象的,所以我们总是从中得到一些东西,如果你打电话给mapped.GetType(),你会发现它实际上是{System.Reflection.Context.Custom.CustomType 1}}而不是您通常会看到的System.RuntimeType。并且CustomType对象属于我的自定义上下文,因此您通过它获取的任何其他反射API对象(例如,如果您编写mapped.Assembly.GetTypes()),您还可以获得经过的自定义对象我的自定义上下文,它有机会修改出来的任何其他内容。

因此,代码可以使用自定义的Type对象在类型系统中导航。即使这样的代码使用普通的基本反射API,我现在也有机会根据自己的需要自定义任何内容。

如果您要求,您只能获得此虚拟化视图。例如,.NET 4.5中的MEF查找自定义属性,指定它应该使用用户提供的自定义反射上下文,否则将回退到普通反射。 (对于我的ShowAllAttributes方法,它使用我选择传入的任何Type对象 - 它不知道它是否获得虚拟化或者#39} ;真正的&#39;类型对象。)

简而言之,这意味着如果您需要虚拟化类型信息,则不再需要围绕反射API的临时包装器。