C# 反射以及实际场景使用

1 什么是反射

首先要复习一下C#的编译过程,可以解释为下图

其中dll/exe中,包括元数据(metadata)和IL(中间语言Intermediate Language)

另外还出现的其他名词:CLR(公共语言运行时,Common Language Runtime)和JIT(实时编译器 Just in Time)

总结: 一个运行的程序查看本身的元数据或其他程序元数据的行为称之为反射

再配合 C# 命名空间和程序集 小记  中的图,来一层层的获取里面的数据

2 读取和使用Assembly

可以使用以下方式获取到Assembly 

// See https://aka.ms/new-console-template for more information
using System.Reflection;

var assembly = Assembly.Load("Reflection");

//Assembly.LoadFile();
//Assembly.LoadFrom();

Console.Read();

2.1 Assmbly实际使用

个人使用Assmbly有以下场景

2.1.1 当程序集存在资源文件时

项目因为存在贴牌,是通过XML文件配置的,因为平时对配置文件管控不足,所以需要和XML嵌入到项目内,一来防止丢失,二来是为了以后通过更新变更配置

var assembly = Assembly.Load("DesktopFramework.Start");

var file = $"DesktopFramework.Start.TemplateFiles.Config.{filename}.xml";

using (var stream = assembly.GetManifestResourceStream(file))
{
    if (stream != null)
    {
        var doc = new XmlDocument();
        doc.Load(stream);
        //处理文件
    }
}

2.1.2 动态加载dll文件

相信很多人都用过Cefsharp,其中对x64和x86的加载由以下代码实现

AppDomain.CurrentDomain.AssemblyResolve += Resolver;

private static Assembly Resolver(object sender, ResolveEventArgs args)
{
    if (args.Name.StartsWith("CefSharp"))
    {
        var assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll";
        var archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
            Environment.Is64BitOperatingSystem ? "x64" : "x86",
            assemblyName);

        return File.Exists(archSpecificPath)
            ? Assembly.LoadFile(archSpecificPath)
            : null;
    }

    return null;
}

2.1.3 获取版本号

烂大街的写法:Assembly.GetExecutingAssembly().GetName().Version.ToString();

3 Type

Type是抽象类,使用这个对象能让我们获取程序的类型信息

  • 对于程序的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
  • 每一个类型都会关联到独立的Type类的对象
  • 不管创建的类型有多少个实例,都只有一个Type对象会关联到所有这些实例

3.1 获取Type

获取Type有两种办法 GetTypes()typeof

假设存在类Test1 Test2

namespace Reflection
{
    public class Test1
    {

    }

    public class Test2
    {

    }
}
using Reflection;
using System.Reflection;

namespace AssemblyReflection
{
    internal static class GetReflection
    {
        public static void GetTypes()
        {
            var assembly = Assembly.Load("Reflection");
            foreach (var value in assembly.GetTypes())
            {
                Console.WriteLine(value.Name);
            }

            /*
             * 输出
             *Test1
             *Test2
             */
        }

        public static void GetTypeName()
        {
            Type type = typeof(Test1);
            Console.WriteLine(type.Name);
        }
    }
}

 4 构造函数和方法

4.1 构造函数和创建实例

在说构造函数之前,先看看怎么通过type构造实例。首先是通过 Activator.CreateInstance(type),然后通过 type.GetConstructor寻找构造方法,再通过Invoke调用。

public static void CreateInstance()
{
    //对默认构造函数
    Type type = typeof(Test3);
    var instance1 = Activator.CreateInstance(type) as Test3;
    Console.WriteLine(instance1?.PublicValue);
}
public static void GetConstructInfo()
{
    Type type = typeof(Test3);
    ConstructorInfo? publicDefaultConstructor = type.GetConstructor(Type.EmptyTypes);
    var instance = publicDefaultConstructor?.Invoke(null);
}

4.2 方法调用和重载方法

有了上面获取构造方法的例子,那么去获取方法也是比较容易。存在一种场景,比如和调用方约定方法名和函数签名,那么调用方只需要使用方法名和参数就可以调用。

首先给出一个类,仅仅函数签名不一样却存在多个方法。

public class Test3
{
    private string PrivateValue { get; set; }

    public string PublicValue { get; set; } = "1";

    public bool PublicBoolValue { get; set; }

    public Test3()
    {
        
    }

    static Test3()
    {
        
    }

    public Test3(string value)
    {
        PublicValue = value;
    }

    public Test3(string value,bool boolValue)
    {
        PublicValue = value;
        PublicBoolValue = boolValue;
    }

    private Test3(string privateValue,string publicValue)
    {
        PrivateValue = privateValue;
        PublicValue = publicValue;
    }

    public void OutPut()
    {
        Console.WriteLine(PublicValue);
    }

    public void OutPut(int param)
    {
        PublicValue += param;
        Console.WriteLine(PublicValue);
    }

    public void OutPut(bool param)
    {
        PublicBoolValue = param;
        Console.WriteLine(PublicValue);
    }
}

按照调用构造方法的模式,很快就可以得到以下代码,很可惜,这段代码无法使用,原因是上述给的方法存在重载,无法通过普通的方式反射得到对应的方法。

public static void Invoke()
{
    Type type = typeof(Test3);
    var instance1 = Activator.CreateInstance(type) as Test3;
    var method = type.GetMethod("OutPut");
    method?.Invoke(instance1, null);
}

我们对寻找方法的代码稍作修改:var method = type.GetMethod("OutPut",new []{typeof(bool)});,这样就可以得到一个指定的方法。

还可以通过 GetParameters获取方法参数。

5 GetFields

有些时候,某些字段是通过 const static保存下来的,需要通过反射得到它们。假设存在一个类

public class GetFields
{
    public static string Key1 = "1";
    public const string Key2 = "2";
}

5.1 BindingFlags

反射当中很多都用到了BindingFlags。可以通过命名知道我们要获取Public的字段,那么

public static void GetClassFields()
{
    FieldInfo[] fields = typeof(Test4).GetFields(BindingFlags.Public |
                                                 BindingFlags.Instance |
                                                 BindingFlags.Static);

    foreach (FieldInfo item in fields)
    {
        string name = item.Name; //名称
        object? value = item.GetValue(typeof(Test4));  //值
    }
}

 

参考链接和文件代码

C#反射中的GetConstructor与GetConstructors构造函数参数的获取

https://github.com/yinghualuowu/blogsCodeSimple/tree/main/AssemblyReflection

posted @ 2022-12-20 01:12  樱花落舞  阅读(404)  评论(0编辑  收藏  举报