c#知识总结-反射

目录

什么是反射

反射的使用场景

反射的性能 

发现程序集中定义的类型

构造类型的实例

反射示例


什么是反射

反射是指在运行时动态的检查、探索和修改程序集中的类型信息和成员信息的能力。反射允许程序在运行时获取程序集的元数据,包括类型、属性、方法,并可以动态的创建对象,调用方法、获取或设置属性等操作,而无需在编译时知道这些类型的具体信息。

反射的使用场景

类型检查和元数据访问

这一类应用涉及到在运行时获取类型的信息,如类的名称、方法、属性、字段等。通过元数据访问,程序可以动态的获取和操作类型信息,实现高度的灵活性。

  • 获取类型信息:包含类名、命名空间、继承层次结构等。
  • 成员访问:访问和操作字段、属性、方法、事件等。

动态对象创建和方法调用

反射最直观的用途是动态的创建对象和调用方法。这使得开发者可以在不知道对象确切类型的情况下,进行对象的实例化和方法调用。

  • 动态对象创建:通过类型名称动态穿件对象实例。
  • 动态方法执行:在运行时调用方法,包括公有、私有方法和重载方法。

动态代理和拦截

反射可以用来实现动态代理和方法拦截,这在很多高级编程场景中非常有用,比如实现AOP(面向切面编程)

  • 动态代理:创建一个对象代理,代理对象可以在目标对象的方法调用前后执行额外的逻辑。
  • 方法拦截:拦截对特定方法的调用,可以用于日志记录、性能监测、事务处理等。
    using System;
    using System.Reflection;
    
    // 定义接口
    public interface ICalculator
    {
        int Add(int a, int b);
    }
    
    // 实际类
    public class Calculator : ICalculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
    
    // 代理类
    public class CalculatorProxy : ICalculator
    {
        private readonly Calculator _calculator;
    
        public CalculatorProxy()
        {
            _calculator = new Calculator();
        }
    
        public int Add(int a, int b)
        {
            // 在调用实际对象方法前执行一些逻辑
            Console.WriteLine("Before calling Add method");
    
            // 使用反射调用实际对象的方法
            MethodInfo methodInfo = typeof(Calculator).GetMethod("Add");
            int result = (int)methodInfo.Invoke(_calculator, new object[] { a, b });
    
            // 在调用实际对象方法后执行一些逻辑
            Console.WriteLine("After calling Add method");
    
            return result;
        }
    }
    
    class Program
    {
        static void Main()
        {
            // 使用代理对象调用方法
            ICalculator calculator = new CalculatorProxy();
            int sum = calculator.Add(3, 5);
            Console.WriteLine("Sum: " + sum);
        }
    }
    

    在上面的示例中,CalculatorProxy 类实现了 ICalculator 接口,并使用反射在调用实际对象的方法前后执行了附加的逻辑。然后,我们使用代理对象 CalculatorProxy 而不是直接调用实际对象 Calculator,以实现对方法调用的拦截和控制。

 自定义属性(Attribute)处理

 反射允许程序检查代码中的自定义属性,这是实现各种框架(如测试框架、ORM框架等)的基础。

  • 属性读取:读取类、方法、字段等上的自定义属性,用于配置或特殊处理。
  • 属性驱动的逻辑:基于自定义属性执行特定逻辑,如序列化/反序列化、数据库操作等。
    using System;
    using System.Reflection;
    
    // 定义一个自定义属性
    [AttributeUsage(AttributeTargets.Class)]
    public class MyAttribute : Attribute
    {
        public string Description { get; }
    
        public MyAttribute(string description)
        {
            Description = description;
        }
    }
    
    // 使用自定义属性
    [My("This is a custom attribute")]
    class MyClass
    {
        // 类的成员和方法
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // 获取 MyClass 类的类型对象
            Type type = typeof(MyClass);
    
            // 获取 MyClass 类上的所有自定义属性
            object[] attributes = type.GetCustomAttributes(typeof(MyAttribute), false);
    
            // 遍历自定义属性并输出其描述信息
            foreach (var attribute in attributes)
            {
                if (attribute is MyAttribute myAttribute)
                {
                    Console.WriteLine("Description: " + myAttribute.Description);
                }
            }
        }
    }
    

    在上面的示例中,我们定义了一个名为 MyAttribute 的自定义属性,并将其应用于 MyClass 类。然后,我们使用反射获取 MyClass 类的类型对象,并使用 GetCustomAttributes 方法获取类上的所有自定义属性。最后,我们遍历自定义属性数组,并输出每个属性的描述信息。

反射的性能 

反射是相当大的机制,允许在运行时发现并使用编译时还不了解的类型及成员。但是他也有下面两个缺点。

  • 反射造成编译时无法保证类型安全性。由于反射严重依赖字符串,所以会丧失编译时
    的类型安全性。例如,执行Type.GetType("nt");要求通过反射在程序集中查找名为
    "int"的类型,代码会通过编译,但在运行时会返回null,因为CLR只知"System.Int32",
    不知"int'"。
  • 反射速度慢。使用反射时,类型及其成员的名称在编译时未知;你要用字符串名称标
    识每个类型及其成员,然后在运行时发现它们。也就是说,使用System.Reflection命
    名空间中的类型扫描程序集的元数据时,反射机制会不停地执行字符串搜索。通常,
    字符串搜索执行的是不区分大小写的比较,这会进一 步影响速度。
     

使用反射 调用成员也会影响性能。用反射调用方法时,首先必须将实参打包(pack)成数组;在内部,反射必须将这些实参解包(unpack)到线程栈上,此外,在调用方法前,CLR必须检查实参具有正确的数据类型。最后,CLR必须确保调动这正确的安全权限来访问被调用的成员。

基于上述原因,最好避免利用反射来范文字段或调用方法/属性。应该利用一下两种技术开发应用程序来动态发现和构造类型实例。

发现程序集中定义的类型

反射经常用于判断程序集定义了哪些类型。FCL提供了许多API来获取这方面的信息。目前最常用的API是Assembly的ExportedTypes属性。示例如下:


using System.Reflection;

String dataAssembly = "System.Data, version=4.0.0.0, culture= neutral, PublicKeyToken=b77a5c561934e089";
LoadAssemAndShowPublicTypes(dataAssembly);

static void LoadAssemAndShowPublicTypes(string assemid)
{
    //显示的将程序集加载到AppDoain中
    var assembly = Assembly.Load(assemid);
    
    //在一个循环汇总显示已加载程序集中每个开导出Type的全名
    foreach (var type in assembly.ExportedTypes)
    {
        //显示全名
        Console.WriteLine(type.FullName);
    }
}


构造类型的实例

获得对Type派生对象的引用后,就可以构造该类型的实例了。FCL提供了以下几个机制。

  • System.Activator的CreateInstance方法

Activator类提供了静态CreateInstance方法的几个重载版本。调用方法时既可传递一
个Type对象引用,也可传递标识了类型的String。直接获取类型对象的几个版本较为
简单。你要为类型的构造器传递一组实参,方法返回对新对象的引用。
用字符串来指定类型的几个版本则稍微复杂一些。首先必须指定另一个字符串来标识
定义了类型的程序集。其次,如果正确配置了远程访问(remoting)选项,这些方法还允
许构造远程对象。第三,这些版本返回的不是对新对象的引用,而是一个
System.Runtime.Remoting.ObjectHandle对象(从System.MarshalByRefObject派生)。
ObjectHandle类型允许将一个 AppDomain中创建的对象传至其他AppDomain,期间
不强迫对象具体化(materialize)。准备好具体化这个对象时,请调用ObjectHandle 的
Unwrap方法。在一个AppDomain中调用该方法时,它将定义了要具体化的类型的程
序集加载到这个AppDomain中。如果对象按引用封送,会创建代理类型和对象。如果
对象按值封送,对象的副本会被反序列化。

using System;

public class Program
{
    public static void Main(string[] args)
    {
        // 要创建实例的类必须具有一个公共的无参数构造函数
        // 这里我们创建了一个名为 Person 的类,它有一个无参数的构造函数
        // 你可以替换为你自己的类
        Type type = typeof(Person);

        // 使用 Activator.CreateInstance 创建 Person 类的实例
        object instance = Activator.CreateInstance(type);

        // 将 object 强制转换为 Person 类型
        Person person = (Person)instance;

        // 现在你可以使用该实例进行操作
        person.Name = "John";
        person.Age = 30;

        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    // 无参数的构造函数
    public Person()
    {
        // 这里可以初始化属性或执行其他操作
    }
}
  • System.Activator的CreatelnstanceFrom方法

Activator类还提供了一组静 态CreateInstanceFrom方法。它们与CreateInstance的
行为相似,只是必须通过字符串参数来指定类型及其程序集。程序集用Assembly 的
LoadFrom(而非Load)方法加载到调用AppDomain中。由于都不接受Type参数,所
以返回的都是一个ObjectHandle对象引用,必须调用ObjectHandle的Unwrap方法
进行具体化。

using System;

public class Program
{
    public static void Main(string[] args)
    {
        // 指定包含要创建类型的程序集的路径
        string assemblyPath = @"C:\Path\To\Your\Assembly.dll";

        // 指定要创建的类型的全名(包括命名空间)
        string typeName = "YourNamespace.YourClass";

        // 使用 Activator.CreateInstanceFrom 创建指定程序集中的指定类型的实例
        object instance = Activator.CreateInstanceFrom(assemblyPath, typeName).Unwrap();

        // 将 object 强制转换为指定类型
        YourNamespace.YourClass obj = (YourNamespace.YourClass)instance;

        // 现在你可以使用该实例进行操作
        obj.SomeMethod();
    }
}
  • System.AppDomain的方法

AppDomain类型提供了4个用于构造类型实例的实例方法(每个都有几个重载版.
本),包括CreateInstance ,CreateInstanceAndUnwrap, CreateInstanceFrom 和
CreatelnstanceFromAndUnwrap。这些方法的行为和Activator 类的方法相似,区别
在于它们都是实例方法,允许指定在哪个AppDomain中构造对象。另外,带Unwrap
后缀的方法还能简化操作,不必执行额外的方法调用。

using System;

class Program
{
    static void Main()
    {
        // 创建一个新的应用程序域
        AppDomain domain = AppDomain.CreateDomain("MyAppDomain");

        // 在新的应用程序域中创建指定类型的实例
        object instance = domain.CreateInstance("AssemblyName", "TypeName");

        // 将 object 强制转换为指定类型
        YourType obj = (YourType)instance;

        // 现在你可以使用该实例进行操作
        obj.SomeMethod();

        // 卸载应用程序域
        AppDomain.Unload(domain);
    }
}
  • System.Reflection.Constructorlnfo的Invoke实例方法

使用一个Type对象引用,可以绑定到一个特定的构造器,并获取对构造器的
ConstructorInfo对象的引用。然后,可利用ConstructorInfo 对象引用来调用它的
Invoke方法。类型总是在调用AppDomain中创建,返回的是对新对象的引用。

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        // 获取要调用的构造函数信息对象
        ConstructorInfo constructorInfo = typeof(YourType).GetConstructor(Type.EmptyTypes);

        // 使用构造函数信息对象调用构造函数实例化对象
        object instance = constructorInfo.Invoke(null);

        // 将 object 强制转换为指定类型
        YourType obj = (YourType)instance;

        // 现在你可以使用该实例进行操作
        obj.SomeMethod();
    }
}
反射示例

包含私有字段、属性、事件、方法的调用

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Reflection;
using System.Text;

var t = typeof(SomeType);
BindToMemberThenInvokeTheMember(t);
Console.WriteLine();

BindToMemberCreateDe1egateToMemberThenInvokeTheMember(t);
Console.WriteLine();

UseDynamicToBindAndInvokeTheMember(t);
Console.WriteLine();



void BindToMemberThenInvokeTheMember(Type t)
{
    Console.WriteLine("BindToMemberThenInvokeTheMember");

    //构造实例
    Type ctorArgument = Type.GetType("System.Int32"); //或者typeof(int32).MakeByRefType();
    ConstructorInfo ctor = t.GetTypeInfo().DeclaredConstructors.First(c => c.GetParameters()[0].ParameterType == ctorArgument);

    object[] args = new Object[] { 12 };//构造器的实参
    Console.WriteLine("x before constructor called:" + args[0]);
    object obj = ctor.Invoke(args);
    Console.WriteLine($"type: {obj.GetType()}");
    Console.WriteLine($"x after constructor returns: {args[0]}");

    //读取字段
    FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField");//或 t.GetField(m_someField);
    //var fi1=t.GetField("m_someField");
    fi.SetValue(obj, 33);
    Console.WriteLine($"x_someField : {fi.GetValue(obj)}");

    //调用方法
    MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
    string s = (String)mi.Invoke(obj, null);
    Console.WriteLine($"Tostring : {s}");

    //读写属性
    PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
    try
    {
        pi.SetValue(obj, 0, null);
    }
    catch (TargetInvocationException e)
    {
        if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw;
        Console.WriteLine("Property set catch.");
        pi.SetValue(obj, 2, null);
        Console.WriteLine($"SomeProp: {pi.GetValue(obj, null)}");
    }

    //为事件添加和删除委托
    EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent");
    EventHandler eh = new EventHandler(EventCallback);
    ei.AddEventHandler(obj, eh);
    ei.RemoveEventHandler(obj, eh);



}

void EventCallback(object sender, EventArgs e) { }
void BindToMemberCreateDe1egateToMemberThenInvokeTheMember(Type t)
{
    Console.WriteLine("BindToMemberCreateDe legateToMemberThenI nvokeTheMember");

    //构造实例(不能创建对构造器的委托)
    object[] args = new object[] { 12 }; //构造器实参
    Console.WriteLine("x before constructor called:" + args[0]);

    object obj = Activator.CreateInstance(t, args);
    Console.WriteLine("Type:" + obj.GetType().ToString());
    Console.WriteLine("x after constructor returns: " + args[0]);

    //注意:不能创建对字段的委托
    //调用方法
    MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
    var toString = mi.CreateDelegate<Func<string>>(obj);
    String s = toString();
    Console.WriteLine("ToString:" + s);

    //读写属性
    PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
    var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj);
    try
    {
        setSomeProp(0);
    }
    catch (ArgumentOutOfRangeException)
    {
        Console.WriteLine("Property set catch.");
    }

    setSomeProp(2);

}

void UseDynamicToBindAndInvokeTheMember(Type t)
{
    Console.WriteLine("UseDynami CToBi ndAndInvokeTheMember");

    //构造实例(不能创建对构造器的委托)
    object[] args = new object[] { 12 }; // 1 构造器的实参
    Console.WriteLine("x before constructor called: " + args[0]);
    dynamic obj = Activator.CreateInstance(t, args);
    Console.WriteLine("Type:" + obj.GetType().ToString());
    Console.WriteLine("x after constructor returns: " + args[0]);
    //读写字段
    try
    {
        obj.m_someField = 5;
        Int32 v = (Int32)obj.m_someField;
        Console.WriteLine("someField:" + v);
    }
    catch (RuntimeBinderException e)
    {
        //之所以会执行到这里,是因为字段是私有的
        Console.WriteLine("Failed to access field: " + e.Message);
    }
    //调用方法
    String s = (String)obj.ToString();
    Console.WriteLine("ToString:" + s);
    //读写属性
    try
    {
        obj.SomeProp = 0;
    }
    catch (ArgumentOutOfRangeException)
    {
        Console.WriteLine("Property set catch.");
    }
    obj.SomeProp = 2;
    Int32 val = (Int32)obj.SomeProp;
    Console.WriteLine("SomeProp:" + val);

    // 从事件增删委托
    obj.SomeEvent += new EventHandler(EventCallback);
    obj.SomeEvent -= new EventHandler(EventCallback);

}

internal static class ReflectionExtensions
{
    //这个辅助扩展方法简化了创建委托的语法
    public static TDelegate CreateDelegate<TDelegate>(this MethodInfo mi, object target = null)
    {
        return (TDelegate)(object)mi.CreateDelegate(typeof(TDelegate), target);
    }
}




//该类用于演示反射机制。
//其中定义了一个字段、构造器、方法、属性和一个事件

internal sealed class SomeType
{
    private Int32 m_someField;
    public SomeType(Int32 x)
    {
        x *= 2;
    }
    public override string ToString()
    {
        return m_someField.ToString();
    }

    public Int32 SomeProp
    {
        get { return m_someField; }
        set
        {
            if (value < 1)
                throw new ArgumentOutOfRangeException("value");
            m_someField = value;
        }
    }

    public event EventHandler SomeEvent;
    private void NoCompilerWarnings()
    {
        SomeEvent.ToString();
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值