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

时间:2009-04-06 21:33:22

标签: c# .net reflection

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

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

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

有许多哲学答案 - “应用程序在看自己” - 但它们没有提供任何实际的提示,说明实际情况或在该情况下如何使用它。

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

18 个答案:

答案 0 :(得分:14)

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

如果您不愿意,

例如:

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

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

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

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

e)反射对于在代码中定义迷你域特定语言(DSL)很方便。

答案 1 :(得分:9)

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

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

pen.DrawLine()

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

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

我不是反射大师,但我将反射用于插件架构。

使用反射我可以在运行时加载.NET程序集(在这种情况下为dll),找出.NET程序集中的所有类型,查看是否有任何类型实现特定的接口,如果是,实例化我调用接口方法的类。

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

答案 2 :(得分:7)

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

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

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

    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作为对象序列化的输出,系统可以将其转换回对象,即使对象本身来自序列化库没有静态链接的库。

如果没有它,有三件事情可以反映出来:

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

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

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

答案 3 :(得分:4)

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

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

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

还有很多其他用法,但我希望这两个用途,打开你对CLR这个优秀功能的启示

答案 4 :(得分:4)

在不知道类在设计时具有哪些属性或方法的情况下,能够读取属性或调用类的方法有时很有用。实现这一目标的方法是反思。如下面的代码所示,您可以获取类的所有属性的列表,并在编译时不知道任何有关它的值的情况下检索它们的值。或者您可以按名称获取方法,即使您在编译时不知道方法的名称,也可以通过反射调用它。例如,这将允许您创建一个脚本语言,该语言对另一个用户提供的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);
  }
}

答案 5 :(得分:4)

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

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

答案 6 :(得分:3)

Reflection正在使用代码来检查代码本身。例如,您可以调用foo.DoBar()而不是呼叫:{/ p>

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

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

有关详细信息,请查看MSDN section on Reflection

答案 7 :(得分:2)

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

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

答案 8 :(得分:2)

<{3}}的作者Andrew Troelsen以这种方式定义了反思:

  

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

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

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

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


为了它的价值,除非你使用反射,否则我不确定它与你的GUI抛出异常有什么关系。

  

关于C#我唯一知道的事情   反思是它很烦人   地狱,因为我所有的GUI应用程序都喜欢   投掷令人难以置信的烦人,毫无意义   “例外已被抛出   调用的目标“例外   Application.Run(new frmMain());,   而不是停在真正的地方   问题发生了(如图所示)   的InnerException)。

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

答案 9 :(得分:2)

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

我看到的一个更合理的用法是在实现C#泛型时使用反射,我想对其他方式不可用的泛型类型进行一些操作。泛型类型上唯一可用的操作是通用约束提供的操作(即,如果您约束泛型类型以实现IFoo,则可以在该类的实例上使用IFoo方法)。有些操作虽然不可用 - 例如我正在使用一个应该具有特定非默认构造函数的泛型类。我不能将泛型方法限制为只接受带有该构造函数的泛型类型参数,但至少我可以在通过反射实现泛型时尝试使用该构造函数。

我谨慎地使用反射,但它偶尔会有用。

答案 10 :(得分:2)

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

答案 11 :(得分:2)

其他人已回答

  

什么是反思,为什么重要

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

  

我该如何使用它?

通过以下命名空间

  

为什么要使用它?

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

当您查看像Reflector这样的产品时,能够检查代码结构的能力远远超过实用

答案 12 :(得分:1)

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

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

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

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

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

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

因此,系统可以处理任何对象(有一个重要的附带条件)。

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

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

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

NB。我在.Net 1.0中实现了它,所以它早于微软的绑定实现 - 我还没有完成调查。

答案 13 :(得分:1)

实际应用

使用反射将数据与对象对齐,如数据映射情况。例如,您可以将数据库列映射到对象的属性。如果你要编写一个Object Relational Mapper,你需要一些方法从配置设置(即DatabaseColumnName映射到Class.MemberName)到它引用的对象。

答案 14 :(得分:0)

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

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

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

答案 15 :(得分:0)

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

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

答案 16 :(得分:0)

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

答案 17 :(得分:0)

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