14

.NET Framework 4.5 Developer Preview 中的新增功能提到

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

的目的是ReflectionContext什么?MSDN 对这个主题不太清楚。

4

1 回答 1

40

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

对这种类型驱动的行为使用反射很有用,因为这意味着每个属性都会显示出来,而控件的开发人员不需要做任何事情。但它提出了一个问题:如果您想自定义事物,例如定义分类或为特定属性自定义编辑 UI,该怎么办?

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

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

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

因此,您会遇到一种情况,有时您需要反射驱动的行为,但有时您希望能够在运行时完全控制。

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

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

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

当然,通过普通的反射 API 看不到这些——VS 和 Blend 有一个位于反射之上的包装层,以使这一切正常工作。

所以现在我们有两个虚拟化层可以通过反射,或者可以增强反射产生的东西......

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

新模型似乎是这样的:ReflectionContext基类是一个抽象 API,通过它您可以获得反射 API 的虚拟化版本。它看似简单,因为其中一个主要思想是,如果您的唯一目标是在反射之​​上获得可虚拟化的包装器,则不再需要像类型描述符系统这样的专用 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(),你会发现它实际上是 aSystem.Reflection.Context.Custom.CustomType而不是System.RuntimeType你通常看到的。并且该CustomType对象属于我的自定义上下文,因此您通过它获得的任何其他反射 API 对象(例如,如果您编写mapped.Assembly.GetTypes())您还将获得通过我的自定义上下文的自定义对象,这将有机会修改任何内容否则出来的。

Type因此代码可以使用自定义对象在类型系统中导航。即使这样的代码使用普通的基本反射 API,我现在也有机会自定义任何我认为合适的东西。

如果您要求,您只会获得此虚拟化视图。例如,.NET 4.5 中的 MEF 查找一个自定义属性,指定它应该使用用户提供的自定义反射上下文,否则将回退到普通反射。(就我的ShowAllAttributes方法而言,它使用Type我选择传入的任何对象——它不知道它获得的是虚拟化对象还是“真实”类型的对象。)

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

于 2012-04-01T11:59:45.237 回答