深入探讨 .NET 系列:学习了解 CLR(3. 元数据)

本文是这个系列第三篇,上一篇简单介绍了一下CLR如何调用托管程序集代码,并且提到了元数据。这篇文章将通过回答几个问题来介绍一下CLR中元数据的相关知识。

元数据

什么是元数据?

概述:
在.NET Framework中,元数据(Metadata)是描述程序代码的数据。它是由编译器生成的结构化信息,包含了关于程序的类型、成员、引用等详细信息。可以理解为程序代码的自描述信息

元数据包括以下内容:

  1. 类型定义:类、结构、接口、枚举等类型的定义信息。
  2. 成员信息:类型的字段、方法、属性、事件等的描述。
  3. 引用信息:程序中引用的其他程序集和类型。
  4. 特性(Attributes):附加在类型或成员上的额外信息。

《CLR via C#》:
元数据是由几个表构成的二进制数据块。有三种表,分别是定义表(definition table)、引用表(reference table)和清单表(manifest table)。表2-1总结了模块元数据块中常用的定义表。
常见元数据定义表
编译器编译源代码时,代码定义的任何东西都导致在表2-1列出的某个表中创建一个记录项。此外,编译器还会检测源代码引用的类型、字段、方法、属性和事件,并创建相应的元数据表记录项。在创建的元数据中包含一组引用表,它们记录了所引用的内容。表2-2总结了常用的引用元数据表。
引用元数据表
引用元数据表续表

摘自《CLR via C# 》第三十四页至三十五页

总结:
在.NET Framework中,元数据被组织成表格结构,这些表格用于描述程序集的详细信息。主要包括元数据定义表、元数据引用表和元数据清单表。
元数据定义表(Metadata Definition Tables)包含定义程序集内所有类型和成员的信息。
元数据引用表(Metadata Reference Tables)包含程序集引用的其他程序集、类型和成员的信息。
元数据清单表(Metadata Manifest Tables)包含程序集的全局信息管理信息

元数据长什么样?

想要查看元数据,可以使用ildasm查看程序集元数据:

  1. 打开 VS命令行工具 (不是普通的CMD,不知道在哪可以打开开始菜单,找到VS的文件夹目录,里面有Developer Command Prompt for VS 2019,我电脑装的是19,这里就用19的了)
  2. 输入:ildasm 回车即可
    打开ildasm
  3. 在ildasm中,点击 “文件 / 打开” 选择自己想查看的程序集即可(编译生成后的exe和dll都可以)
    我这里准备了一个测试的控制台应用程序,写了一点测试代码,并且生成了exe
using System;

namespace ConsoleApp1
{
    class Program
    {
        private static int Test_Number = 0;

        enum Status
        {
            Enabled,
            Disabled
        }

        static void Main(string[] args)
        {
            TestMethod("Hello World!");
        }

        public static void TestMethod(string msg)
        {
            Console.WriteLine(msg);
        }
    }

    class YuG_Meta_Test_Class
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public DateTime Birthday { get; set; }
    }
}

打开后:
IL代码
4. 点击 “视图 / 元信息 / 显示!” 即可查看元数据
元数据
我这里复制了一些出来:

===========================================================
ScopeName : ConsoleApp1.exe
MVID      : {19014EF9-ADE8-4471-9196-737DAAA3EE64}
===========================================================
Global functions
-------------------------------------------------------

Global fields
-------------------------------------------------------

Global MemberRefs
-------------------------------------------------------

TypeDef #1 (02000002)
-------------------------------------------------------
	TypDefName: ConsoleApp1.Program  (02000002)
	Flags     : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100000)
	Extends   : 01000010 [TypeRef] System.Object
	Field #1 (04000001)
	-------------------------------------------------------
		Field Name: Test_Number (04000001)
		Flags     : [Private] [Static]  (00000011)
		CallCnvntn: [FIELD]
		Field type:  I4

	Method #1 (06000001) [ENTRYPOINT]
	-------------------------------------------------------
		MethodName: Main (06000001)
		Flags     : [Private] [Static] [HideBySig] [ReuseSlot]  (00000091)
		RVA       : 0x00002050
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		ReturnType: Void
		1 Arguments
			Argument #1:  SZArray String
		1 Parameters
			(1) ParamToken : (08000001) Name : args flags: [none] (00000000)

	Method #2 (06000002) 
	-------------------------------------------------------
		MethodName: TestMethod (06000002)
		Flags     : [Public] [Static] [HideBySig] [ReuseSlot]  (00000096)
		RVA       : 0x0000205e
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		ReturnType: Void
		1 Arguments
			Argument #1:  String
		1 Parameters
			(1) ParamToken : (08000002) Name : msg flags: [none] (00000000)

TypeDef #2 (02000003)
-------------------------------------------------------
	TypDefName: ConsoleApp1.YuG_Meta_Test_Class  (02000003)
	Flags     : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100000)
	Extends   : 01000010 [TypeRef] System.Object
	Field #1 (04000002)
	-------------------------------------------------------
		Field Name: <Name>k__BackingField (04000002)
		Flags     : [Private]  (00000001)
		CallCnvntn: [FIELD]
		Field type:  String
		CustomAttribute #1 (0c00000f)
		-------------------------------------------------------
			CustomAttribute Type: 0a00000f
			CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
			Length: 4
			Value : 01 00 00 00                                      >                <
			ctor args: ()

	Property #1 (17000001)
	-------------------------------------------------------
		Prop.Name : Name (17000001)
		Flags     : [none] (00000000)
		CallCnvntn: [PROPERTY]
		hasThis 
		ReturnType: String
		No arguments.
		DefltValue: 
		Setter    : (06000005) set_Name
		Getter    : (06000004) get_Name
		0 Others

	Property #2 (17000002)
	-------------------------------------------------------
		Prop.Name : Age (17000002)
		Flags     : [none] (00000000)
		CallCnvntn: [PROPERTY]
		hasThis 
		ReturnType: I4
		No arguments.
		DefltValue: 
		Setter    : (06000007) set_Age
		Getter    : (06000006) get_Age
		0 Others

	Property #3 (17000003)
	-------------------------------------------------------
		Prop.Name : Birthday (17000003)
		Flags     : [none] (00000000)
		CallCnvntn: [PROPERTY]
		hasThis 
		ReturnType: ValueClass System.DateTime
		No arguments.
		DefltValue: 
		Setter    : (06000009) set_Birthday
		Getter    : (06000008) get_Birthday
		0 Others


TypeDef #3 (02000004)
-------------------------------------------------------
	TypDefName: Status  (02000004)
	Flags     : [NestedPrivate] [AutoLayout] [Class] [Sealed] [AnsiClass]  (00000103)
	Extends   : 01000011 [TypeRef] System.Enum
	EnclosingClass : ConsoleApp1.Program (02000002)
	Field #1 (04000005)
	-------------------------------------------------------
		Field Name: value__ (04000005)
		Flags     : [Public] [SpecialName] [RTSpecialName]  (00000606)
		CallCnvntn: [FIELD]
		Field type:  I4

	Field #2 (04000006)
	-------------------------------------------------------
		Field Name: Enabled (04000006)
		Flags     : [Public] [Static] [Literal] [HasDefault]  (00008056)
	DefltValue: (I4) 0
		CallCnvntn: [FIELD]
		Field type:  ValueClass Status

	Field #3 (04000007)
	-------------------------------------------------------
		Field Name: Disabled (04000007)
		Flags     : [Public] [Static] [Literal] [HasDefault]  (00008056)
	DefltValue: (I4) 1
		CallCnvntn: [FIELD]
		Field type:  ValueClass Status


TypeRef #1 (01000001)
-------------------------------------------------------
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.CompilationRelaxationsAttribute
	MemberRef #1 (0a000001)
	-------------------------------------------------------
		Member: (0a000001) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  I4

TypeRef #2 (01000002)
-------------------------------------------------------
Token:             0x01000002
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
	MemberRef #1 (0a000002)
	-------------------------------------------------------
		Member: (0a000002) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

Assembly
-------------------------------------------------------
	Token: 0x20000001
	Name : ConsoleApp1
	Public Key    :
	Hash Algorithm : 0x00008004
	Version: 1.0.0.0
	Major Version: 0x00000001
	Minor Version: 0x00000000
	Build Number: 0x00000000
	Revision Number: 0x00000000
	Locale: <null>
	Flags : [none] (00000000)
	CustomAttribute #1 (0c000001)
	-------------------------------------------------------
		CustomAttribute Type: 0a000001
		CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32)
		Length: 8
		Value : 01 00 08 00 00 00 00 00                          >                <
		ctor args: (8)

AssemblyRef #1 (23000001)
-------------------------------------------------------
	Token: 0x23000001
	Public Key or Token: b7 7a 5c 56 19 34 e0 89 
	Name: mscorlib
	Version: 4.0.0.0
	Major Version: 0x00000004
	Minor Version: 0x00000000
	Build Number: 0x00000000
	Revision Number: 0x00000000
	Locale: <null>
	HashValue Blob:
	Flags: [none] (00000000)

User Strings
-------------------------------------------------------
70000001 : (12) L"Hello World!"

Coff symbol name overhead:  0
===========================================================
===========================================================
===========================================================

上面是反编译出来的元数据信息,实际要比我展示出来的多很多,但是由于原本的太长了,就挑选了一部分出来,感兴趣的可以自己反编译一下,看看全部的长什么样。

仔细查看上面的元数据,就能发现:在测试代码中定义的类,属性,枚举,方法等都可以在元数据中找到。用编写的测试方法举例,仔细查看IL代码和元数据,你是不是能发现些什么?
main方法的IL代码:
main方法的IL代码
TestMethod方法的元数据:

	Method #2 (06000002) 
	-------------------------------------------------------
		MethodName: TestMethod (06000002)
		Flags     : [Public] [Static] [HideBySig] [ReuseSlot]  (00000096)
		RVA       : 0x0000205e
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		ReturnType: Void
		1 Arguments
			Argument #1:  String
		1 Parameters
			(1) ParamToken : (08000002) Name : msg flags: [none] (00000000)

仔细查看IL代码中,红色箭头标识的编号,通过这个编号去元数据中查找一下,你是不是就能发现其中的奥秘了。

元数据有什么作用?

元数据在.NET Framework中有许多重要作用:

  1. 自描述:程序包含自己的描述信息,允许运行时和工具在没有源代码的情况下理解程序结构。
  2. 反射(Reflection):反射是一个强大的特性,允许程序在运行时检查和操作自身的元数据。
  3. 类型安全:元数据确保了类型的安全性,运行时可以检查类型兼容性和安全性。
  4. 序列化和反序列化:元数据帮助进行对象的序列化和反序列化过程。
  5. 代码生成和编译:编译器和工具可以使用元数据生成和编译代码。

元数据存储在哪?

元数据被存储在程序集(Assembly)中,与IL(中间语言)代码一起打包。具体来说,元数据位于程序集的CLR头(CLI Header)部分。

程序集的结构通常包括以下部分:

  1. CLR头:包含元数据、模块信息等。
  2. IL代码:由编译器生成的中间语言代码。
  3. 资源:嵌入的图像、字符串等资源。

元数据什么时候加载使用?

元数据在以下情况中会被加载:

  1. 程序集加载时:当程序集被加载到内存中,CLR会读取元数据来了解类型和成员信息。
  2. 反射操作时:当使用反射API(如Type.GetType、MethodInfo.Invoke等)时,CLR会访问元数据。
  3. 运行时类型检查时:在进行类型检查、方法调用等操作时,CLR会检查元数据确保操作的合法性。

为什么需要元数据?

元数据对于.NET Framework的运行时和开发过程至关重要,原因包括:

  1. 运行时类型信息:提供了丰富的类型信息,支持运行时的动态类型检查和方法调用。
  2. 工具支持:开发工具(如Visual Studio)利用元数据提供智能感知、自动补全等功能。
  3. 互操作性:元数据使得.NET代码能够与COM、PInvoke等进行互操作。
  4. 安全性:通过元数据,CLR能够执行类型和安全性验证,防止非法操作。

总结

元数据在.NET Framework中起到了关键作用,提供了丰富的类型信息,支持了开发工具和运行时的多种功能,确保了程序的类型安全和运行时操作的合法性。

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值