C#:有人可以解释反思的实用性吗?

所以我试着搜索,希望有人对此有一个很好的解释,没有运气。

我问了我的另一个朋友一个不同的问题(我现在已经忘记了),他的回答只是在他签字之前的“反思”。

我仍然是C#世界的新手,曾经是业余的VB.net程序员(也是JavaScript,ActionScript和C),我正在尽我所能去掌握这些先进的概念。

有许多哲学答案 – “应用程序看自己” – 但它们没有提供任何实际的提示,说明实际发生的事情或在该环境中如何使用它。

那么,什么是反思,为什么重要,以及为什么/如何使用它?

Reflection提供了在运行时确定事物和执行代码的能力。

如果您不想使用它,则不必使用它,但它对动态行为非常方便。

例如:

a)您可以使用reflection来配置应用程序,方法是加载外部配置文件并根据它启动服务。 您的应用程序不必事先知道实现这些服务的类,只要它们符合特定的接口或API即可。

b)使用reflection,您可以动态生成类和代码,这简化了某些编程任务,因为程序员不必显式创建所有需要的代码。

c)对于通过检查代码工作的程序来说,反思也是非常宝贵的。 一个例子是IDE或UI设计者。

d)reflection可以帮助您减少样板代码。

e)reflection在您的代码中定义迷你域特定语言(DSL)很方便。

(我的定义)reflection是能够编写在运行时执行代码的静态代码,通常在编译时确定。

例如,我可以通过编译该命令来调用类方法来绘制,例如:

pen.DrawLine() 

或者通过reflection,我可以先看看我的对象是否有一个名为“drawline”的方法,如果有,请调用它。 (注意这不是实际的C#Reflection语法)

  if(pen.Methods.Contains("DrawLine")) { pen.InvokeMethod("DrawLine")) } 

我不是reflection大师,但我使用reflection作为插件架构。

通过reflection,我可以在运行时加载.NET程序集(在本例中为dll),找出.NET程序集中的所有类型,查看是否有任何类型实现特定的接口,如果是,则实例化我调用接口方法的类。

我知道用例有点技术性,但本质上reflection允许我动态加载插件(即在运行时),并让我对它进行类型安全的调用。

reflection最常见的用途是以前称为RTTI(运行时类型信息)的扩展,主要是C ++程序员的领域。

reflection是.net构建方式的副作用,Microsoft选择将其用于创建Visual Studio的库和.net运行时公开给Microsoft以外的开发人员。

大多数reflection库都侧重于可以在运行时调用的类型发现和创建。 这允许一些非常强大的自引用代码。 以下示例位于我们的配置管理系统的核心(为清晰起见,删除了一些位):

  public static IMyCompanySetting UnwrapSetting(XmlNode settingNode) { string typeName = settingNode.Attributes["type"].Value; string typeAssembly; if(settingNode.Attributes["assembly"] != null) { typeAssembly = settingNode.Attributes["assembly"].Value; } Type settingType = null; Assembly settingAssembly = null; try { // Create an object based on the type and assembly properties stored in the XML try { settingAssembly = Assembly.Load(typeAssembly); if (settingAssembly == null) { return null; } } catch (Exception outerEx) { try { settingType = GetOrphanType(typeName); } catch (Exception innerEx) { throw new Exception("Failed to create object " + typeName + " :: " + innerEx.ToString(), outerEx); } } // We will try in order: // 1. Get the type from the named assembly. // 2. Get the type using its fully-qualified name. // 3. Do a deep search for the most basic name of the class. if (settingType == null && settingAssembly != null) settingType = settingAssembly.GetType(typeName); if (settingType == null) settingType = Type.GetType(typeName); if (settingType == null) settingType = GetOrphanType(typeName); if (settingType == null) throw new System.Exception( String.Format("Unable to load definition for type {0} using loosest possible binding.", typeName)); } catch (Exception ex) { throw new CometConfigurationException( String.Format("Could not create object of type {0} from assembly {1}", typeName, typeAssembly), ex); } bool settingIsCreated = false; IMyCompanySetting theSetting = null; // If the class has a constructor that accepts a single parameter that is an XML node, // call that constructor. foreach (ConstructorInfo ctor in settingType.GetConstructors()) { ParameterInfo[] parameters = ctor.GetParameters(); if (parameters.Length == 1) { if (parameters[0].ParameterType == typeof(XmlNode)) { object[] theParams = { settingNode }; try { theSetting = (IMyCompanySetting)ctor.Invoke(theParams); settingIsCreated = true; } catch (System.Exception ex) { // If there is a pre-existing constructor that accepts an XML node // with a different schema from the one provided here, it will fail // and we'll go to the default constructor. UtilitiesAndConstants.ReportExceptionToCommonLog(ex); settingIsCreated = false; } } } } 

这段代码允许我们创建无限数量的类来实现IMyCompanySetting,并使用XML对它们进行序列化和反序列化。 然后,给定一块XML作为对象序列化的输出,系统可以将其转换回对象,即使对象本身来自序列化库没有静态链接的库。

如果没有它,有三件事反映在这里是不可能的:

在运行时根据名称加载程序集。

根据程序名称在运行时从程序集加载对象。

根据编译时未知的类对象的签名调用对象构造函数。

假设您有两个接口的替代实现。 您希望允许用户通过简单的文本配置文件选择一个或另一个。

通过reflection,您可以从配置文件(作为字符串)简单地读取要使用其实现的类的名称,并实例化该类的实例。

reflection可以让你深入研究一个组件并使用它,无论你以后没有引用它。

考虑一个插件系统,主机不知道它将容纳的插件; 使用Reflection(以及正确的架构),您可以构建一个实现此目的的简单解决方案。

考虑另一种场景,您必须在给定字符串的对象上调用该方法,Reflection还为您提供了实现此目的的方法。

还有很多其他的用法,但我希望这两个用法,打开你对CLR的这个优秀function的apetite

有时能够在不知道类在设计时具有哪些属性或方法的情况下读取属性或调用类的方法是有用的。 实现这一目标的方法是反思。 如下面的代码所示,您可以获取类的所有属性的列表,并在编译时不知道任何有关它的值的情况下检索它们的值。 或者您可以按名称获取方法,即使您在编译时不知道方法的名称,也可以通过reflection调用它。 例如,这将允许您创建一个脚本语言,该语言对另一个用户提供的DLL中定义的对象进行操作。 (当然,您也可以枚举类中的所有方法或按名称检索特定属性,但这些情况不会在下面的代码中演示。)

 class Program { static void Main(string[] args) { UserDefinedClass udc = new UserDefinedClass(); udc.UserProperty = "This is the property value"; ClassTracer ct = new ClassTracer(udc); ct.TraceProperties(); ct.CallMethod("UserFunction", "parameter 1 value"); } } class ClassTracer { object target; public ClassTracer(object target) { this.target = target; } public void TraceProperties() { // Get a list of all properties implemented by the class System.Reflection.PropertyInfo[] pis = target.GetType().GetProperties(); foreach (System.Reflection.PropertyInfo pi in pis) { Console.WriteLine("{0}: {1}", pi.Name, pi.GetValue(target, null)); } } public void CallMethod(string MethodName, string arg1) { System.Reflection.MethodInfo mi = target.GetType().GetMethod(MethodName); if (mi != null) { mi.Invoke(target, new object[] { arg1 }); } } } class UserDefinedClass { private string userPropertyValue; public string UserProperty { get { return userPropertyValue; } set { userPropertyValue = value; } } public void UserFunction(string parameter) { Console.WriteLine("{0} - {1}", userPropertyValue, parameter); } } 

reflection是使用代码来检查代码本身。 例如,您可以调用以下foo.DoBar()而不是调用foo.DoBar()

 foo.GetType().GetMethod("DoBar").Invoke(foo,null); 

这似乎是一种到达同一目的地的循环方法,但它也可以用来做一些可能破坏规则的黑魔法,比如在框架类上调用私有方法。 它还可以用于实际生成代码并输出新的二进制文件,以及一小部分对一小群人非常有用的东西。

有关更多信息,请查看有关Reflection的MSDN部分 。

在许多情况下,应用程序不应该对自己有很多假设,应该查看运行时以查看它实际上是什么。 这是反映进入节目的地方。 例如,ASP.NET不会假设您使用的成员资格提供程序,只是它inheritance了相应的类。 它使用reflection在运行时查找类并实例化它。 由于解耦和减少类之间的依赖关系,这提供了大量的可扩展性。

当然,这只是reflection的一个用例。 可能还有许多其他情况可能非常有用。

Pro C#2008和.NET 3.5平台的作者Andrew Troelsen以这种方式定义了reflection:

在.NET Universe中, reflection是运行时类型发现的过程。

我不确定我能否准确地解释这意味着什么,但我可以告诉你我是如何使用它的。

我可以在程序集(dll)上放置用户定义的属性。 在运行时使用reflection,我可以检查某个目录位置中的所有程序集,以查看它们是否已定义此属性。 这可以让我的应用程序了解如何使用程序集,比如插件。

我还使用了reflection来加载和保存应用程序设置。


对于它的价值,除非你使用reflection,否则我不确定它与你的GUI抛出exception有什么关系。

关于C#reflection我唯一知道的是它很烦人,因为我所有的GUI应用程序都喜欢抛出令人难以置信的恼人,毫无意义的“Exception已经被调用的目标抛出”Application.Runexception(new frmMain() );而不是停止发生真正问题的地方(如innerException所示)。

您的声明让我相信您在应用程序中的任何位置都有很少的try / catch块,这样每个exception都会渗透回调用堆栈的顶部。 如果在调试模式下使用Visual Studio 2005/2008,请进入“调试”菜单并选择“例外…”菜单选项。 在“例外”对话框中,选中“投掷”列下的复选框。 现在,当您运行应用程序并抛出exception时,调试器将中断抛出exception的位置。

假设您有一些业务实体,它们都来自一个名为Entity的基类。 我们还要说你需要/希望所有派生类都是可克隆的。 您可以在基类上实现一个方法“Clone”(接口ICloneable),该方法将循环遍历当前类的所有属性(尽管它在Entity类中实现)并将它们复制到克隆对象。 这是一个reflection可以真正帮助的情况。 因为您无法知道基类的属性的名称和计数。 而且您不希望在所有派生类中实现该方法。 但是,您可能希望将该方法设为虚拟。

其他人已经回答了

什么是反思,为什么重要

所以我只会回答以下问题。

我该如何使用它?

通过以下命名空间

  • 的System.Reflection
  • System.Reflection.Emit

我为什么要用它?

调用/检查程序集类型,成员,属性

当你看一下像Reflector这样的产品时,能够检查代码结构是非常实用的。

如果你跑得快而且愤怒,也许你是从一个对象inheritance而需要访问一个私有领域。 你没有来源,但没关系,你有反思。 (哈基,我知道)

我看到的一个更合理的用法是在实现C#generics时使用reflection,我希望对通用类型执行一些操作,否则这些操作不可用。 generics类型上唯一可用的操作是通用约束提供的操作(即,如果您约束generics类型以实现IFoo,则可以在该类的实例上使用IFoo方法)。 有些操作虽然不可用 – 例如我正在使用一个应该具有特定非默认构造函数的generics类。 我不能将generics方法限制为仅接受具有该构造函数的generics类型参数,但至少我可以尝试在通过reflection实现generics时使用该构造函数。

我谨慎地使用reflection,但它偶尔会得到真正的方便。

我使用reflection来实现自定义数据绑定系统。

例如,使用我的绑定API,我可以编写以下内容:

绑定b = new Binding(textBox,“Text”,myObject,“Text”,BindingMode.Bidirectional);

对textBox对象中的文本的任何更改都由Binding对象(附加到TextChanged事件)检测并传递到myObject.Text属性。 检测到myObject.Text的更改(通过其TextChanged事件)并传递给textBox.Text成员。

我使用reflection实现了这个系统。 Binding构造函数声明为:

绑定(对象,字符串,对象,字符串,BindingMode)

因此,该系统适用于任何对象(具有一个重要的附带条件)。

我检查第一个对象,并在第一个字符串中找到与指定成员对应的成员(textBox和“Text”,即textBox有一个名为“Text”的成员)。 重复第二个对象和字符串。

Proviso:对于要在此方案中使用的对象,它们必须实现非正式要求,即任何可绑定属性必须具有相应的PropertyNameChanged事件。 令人高兴的是,几乎所有的.Net UI组件都遵循这一惯例。

然后,我检查对象是否有必要的PropertyNameChanged事件,向它们添加事件处理程序,并设置所有内容。

NB。 我在.Net 1.0中实现了这个,所以它早于微软的绑定实现 – 我还没有进行调查。

实际应用

使用reflection将数据与对象对齐,如在数据映射情况中那样。 例如,您可以将数据库列映射到对象的属性。 如果您要编写对象关系映射器,则需要某种方法从配置设置(即DatabaseColumnName映射到Class.MemberName)到它引用的对象。

reflection是如此命名,因为它允许一些代码看起来就像在镜子中看一样。

如前所述,reflection可用于使用类名创建新实例。 序列化也由reflection提供支持。 在这种情况下,代码检查所有可序列化字段并递归地序列化这些字段,直到整个图形被消耗并且其字节forms被写入。

Reflection允许通过在运行时发现类型,方法,字段等来在编译时转义静态类型。 JVM上的许多动态语言(不确定CLR但id想象同样如此)使用reflection来调度方法。

就reflection的实际用途而言,我们使用它来允许我们的客户提供他们自己的本地化。

通过我们的应用程序,我们提供了一个本地资源数据库,客户端可以通过为所有字符串,菜单项,对话框控件等提供语言翻译来工作。 使用此客户端翻译的数据库,我们然后在应用程序启动时使用reflection并遍历所有控件并使用客户端提供的翻译字符串替换默认语言字符串。

这里已经详细介绍了reflection的许多用途,但另一种用途可能是记录对象的状态,包括其私有成员以用于调试。

你能发布一些示例代码来说明你是如何进行reflection的吗? 您是否将PDB文件放在与使用reflectionAPI的程序集相同的位置? 如果是这样,那么在Visual Studio中转到Debug菜单 – > Exceptions并选中复选框“Common Language runtime thrown”。 以调试模式运行应用程序。 希望调试器应该在您reflection的程序集中突破真正的exception时突破。