首页 > 编程语言 >《NET CLR via C#》---第六章(类型成员,类型的可访问性,友元程序集,分部类型,CLR调用方法指令)

《NET CLR via C#》---第六章(类型成员,类型的可访问性,友元程序集,分部类型,CLR调用方法指令)

时间:2024-08-14 20:04:44浏览次数:15  
标签:友元 编译器 实例 类型 方法 public CLR

类型成员

类型可以定义0个或者多个以下种类的成员。

成员 描述
常量 常量是指出数据值恒定不变的符号。这种符号使代码更易阅读和维护。常量总与类型关联,不与类型的实例关联。常量总与类型关联,不与类型的实例关联
字段 字段表示只读或可读/可写的数据值。字段可以是静态的:这种字段被认为是类型状态的一部分。字段也可以是实例(非静态);这种字段被认为是对象状态的一部分。强烈建议将字段声明为私有,防止类型或对象的状态被类型外部的代码破坏。
实例构造器 实例构造器是将新对象的实例字段初始化为良好初始状态的特殊方法。
类型构造器 类型构造器是将类型的静态字段初始化为良好初始状态的特殊方法。
方法 方法是用来改变或获取对象或类型信息的函数。作用于类型称为静态方法,作用于对象称为实例方法。
操作重载符 操作符重载实际是方法,定义了当操作符作用于对象时,应该如何操作该对象。操作重载符不属于CLS的一部分(不是所有编程语言都支持)
转换操作符 转换操作符是定义如何隐式或显式将对象从一种类型转型为另一种类型的方法。转换操作符不属于CLS的一部分(不是所有编程语言都支持)
属性 属性让你用简单的语法像访问字段一样读取或修改对象的信息,同时确保数据的完整性。作用于类型称为静态函数,作用于对象称为实例属性。属性可以无参,也可以有多个参数。
事件 静态事件允许类型向一个或多个静态或实例方法发送通知。实例对象允许对象向一个或多个静态或实例方法发送通知。
类型 类型可以定义其他嵌套类型 。

无论什么编程语言,编译器都必须能处理源代码,为上述每种成员生成元数据和IL代码。所有编程语言生成的元数据格式完全一致。这正式CLR成为“公共语言运行时”的原因。元数据是所有语言都生成和使用的公共信息。CLR还利用公共元数据格式决定常量、字段、构造器、方法、属性和事件在运行时的行为。简单来说,元数据是整个Microsoft .NET Framework开放平台的关键,它实现了编程语言、类型和对象的无缝集合。

public class MainClass
{
    private class SubClass { }              // 嵌套类

    private const int constVal = 1;         // 常量
    private readonly int readonlyVal = 2;   // 只读
    private static int staticVal = 3;       // 静态字段

    static MainClass() { }                  // 类型构造器

    public MainClass() { }                  // 实例构造器-无参

    public MainClass(int val) { }           // 实例构造器-带参

    private void ObjMethod() { }            // 实例方法
    
    private static void StaticMethod() { }  // 静态方法

    public int prop { get; set; }           // 实例属性

    public int this[int index] { get => 0;set { } } // 实例有参属性(索引器)

    public event EventHandler SomeEvent;    // 实例事件
}

编译这个类型,并用ILDasm.exe查看下元数据,看看编译是如何将类型极其成员转为元数据:
image

类型的可访问性

CLR自己定义了一组可访问性修饰符,但每种编程语言在向成员应用可访问性时,都选择了自己的一组术语以及相应的语法。

CLR术语 C#术语 描述
Private private 成员只能由定义类型或任何嵌套类型中的方法访问
Family protected 成员只能由定义类型、任何嵌套类型或者不管在什么程序集中的派生类型中方法访问
Family and Assembly 不支持 成员只能由定义类型、任何嵌套类型或者同一程序集中的派生类型中方法访问
Assembly internal 成员只能由定义程序集中的方法访问
Family or Assembly protected internal 成员可由任何嵌套类型,任何派生类型(同一或不同程序集都可)中的任何方法访问
Public public 成员可由任何程序集的任何方法访问

编译代码时,编程语言的编译器检查代码是不是正确引用了类型和成员。如果代码不正确地引用了类型或成员,编译器会生成一条合适的错误信息。

在C#中,如果没有显式声明成员的可访问性,编译器通常(但不总是)默认选择private。CLR要求所有接口类型的所有成员都具有public可访问性。

派生类重写基类定义的成员时,C#编译器要求原始成员和重写成员具有相同的可访问性,即基类成员是protected,子类也要是protected。CLR则允许放宽但不能收紧成员的可访问性,即基类是protected,子类可以是public。这是因为CLR承诺派生类总能转为基类,并获取对基类方法的访问权。

友元程序集

public类型不仅对定义程序集中的所有代码可见,还对其他程序集中的代码可见。internal则仅对定义程序集中的所有代码可见,对其他程序集中的代码不可见。

生成程序集时,可用System.Runtime.CompilerServices命名空间中的InternalsVisibleTo特性标明它认为是“友元”的其他程序集。该特性获取标识友元程序集名称和公钥的字符串参数。注意当程序集认了“友元”之后,友元程序集就能访问该程序集中的所有internal类型,以及这些类型的internal成员。这在需要共享程序集之间的内部实现细节时特别有用,比如单元测试时需要访问类的内部实现。

要定义友元程序集,你需要在被访问的程序集的 AssemblyInfo.cs 文件中使用 InternalsVisibleTo 属性指定友元程序集的名称。假设我们有一个名为 MainAssembly 的主程序集,其中包含一个类 MyClass,该类具有一个 internal 方法。

// MainAssembly - AssemblyInfo.cs
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("FriendAssembly")]

namespace MainAssembly
{
    public class MyClass
    {
        internal void InternalMethod()
        {
            Console.WriteLine("Internal method in MainAssembly.");
        }
    }
}

接下来,我们创建一个名为 FriendAssembly 的友元程序集,它可以访问 MainAssembly 的内部成员。

// FriendAssembly - Program.cs
using MainAssembly;

namespace FriendAssembly
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();
            myClass.InternalMethod(); // 访问 internal 方法
        }
    }
}

需要注意的是,如果 MainAssembly 使用了强名称签名,那么在使用 InternalsVisibleTo 属性时,你需要指定友元程序集的公共密钥(PublicKey)。示例如下:

[assembly: InternalsVisibleTo("FriendAssembly, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d537e8bda...")]

静态类

有一些永远不需要实例化的类,例如Console,Math等。这些类只有static成员。事实上,这种类唯一的作用就是组合一组相关的成员。例如,Math类就定义了一组执行数学运算的方法。在C#中,要用static关键字定义不可实例化的类。该关键字只能用于类,不能应用于结构(值类型)。因为CLR总是允许值类型实例化,这无法避免。

C#编译器对静态类做了如下限制:

  • 静态类必须直接从System.Object派生,从其他任何基类派生都没有意义。继承只适用于对象,而你不能创建静态类的实例。
  • 静态类不能实现任何接口,这是因为只有使用类的实例时,才可调用类的接口方法。
  • 静态类只能定义静态成员(字段、方法、属性和事件),任何实例成员都会导致编译器报错。
  • 静态类不能作为字段、方法参数或局部变量使用,因为它们都代表引用了实例的变量。

编译如下代码:

public static class MainClass
{
    public static int val;
}

使用ILDasm.exe查看程序集,能够发现使用关键字static定义类,将导致C#编译器将该类标记为abstract和sealed。另外,编译器不在类型中生成实例构造器方法(.ctor)
image

分部类型

partial关键字告诉C#编译器:类、结构或接口的定义源代码可能要分散到一个或多个源代码文件中。有如下几点好处:

  • 源代码控制:没法多个程序员同时对一个类型进行修改,使用partial关键字可以将类型的代码分散到多个源代码文件中,每个文件都可以单独签出,多个程序员能同时编辑类型。
  • 在同一个文件中将类或结构分解成不同的逻辑单元:分部的每个部分都能实现一个功能,并配以它的全部字段、方法、属性、事件等。这样就可以方便地看到组合以提供一个功能的全体成员,从而简化编码。
  • 代码拆分:新建一个类型时,可以自动生成一部分代码。这些代码可以我们自己的代码拆分到不同的源代码文件中。避免自动生成和我们自己的代码互相干扰。

“分部类型”功能完全由C#编译器实现,CLR对该功能一无所知。这也解释了一个类型的所有源代码文件为什么必须使用相同编程语言,而且必须作为一个编译单元编译到一起。

CLR调用方法指令

以下Employee类定义了3种不同的方法:

internal class MainClass
{
    public void CustomMethod() { }
    public void VirtualMethod() { }
    public void StaticMethod() { }
}

编译上述代码,编译器会在程序集的方法定义表中写入3个记录项,每个记录项都用一组flag指明方法是实例方法、虚方法还是静态方法。
image
写代码调用这些方法,生成调用代码的编译器会检查方法定义的flag,判断如何生成IL代码来正确调用方法。

  • call
    在IL指令中,call指令用于调用方法,可以是静态方法、实例方法或虚方法。
    调用静态方法:必须明确指出在哪个类型中定义了该方法。
    调用实例方法或虚方法:需要指定一个引用了对象的变量,并且假设这个变量不是null(即,变量必须有一个有效的对象)。变量的类型会确定方法属于哪个类。如果变量的类型中没有定义这个方法,就会在其基类中查找合适的方法。
    此外,call指令通常用于以非虚拟方式调用虚方法。
  • callvirt
    callvirt是IL指令的一种,用于调用实例方法和虚方法,不能用于静态方法。
    调用实例方法或虚方法:需要指定一个引用对象的变量。对于非虚实例方法,变量的类型决定了调用哪个类中的方法。
    调用虚方法:CLR(公共语言运行时)会检查对象的实际类型,并以多态方式调用正确的方法。
    为确保对象存在,变量不能是null。编译时,JIT编译器会生成代码来检查变量是否为null。如果是null,callvirt指令会引发NullReferenceException异常。
    由于这种额外的null检查,callvirt指令的执行速度比call指令稍慢。即使callvirt指令调用的是非虚方法,也会进行null检查。

标签:友元,编译器,实例,类型,方法,public,CLR
From: https://www.cnblogs.com/chenxiayun/p/18355692

相关文章

  • C程序设计(安徽专升本3.2基本数据类型)
    一、数据类型的分类 在本章节我们之讲解基础的数据类型,因为后续的数据类型将会单独对此讲解,常考的为基本数据类型,数组,函数,指针这几种类型!其它类型作为了解,认识即可二、整型类型此处对整数类型的讲解排除字符型和布尔型,它们单独拉出讲解,且我不喜欢废话讲解,我直接列表加代码......
  • ABP默认模板修改默认数据库类型并初始化数据库数据
    我这里以SQLite数据库为例,其他数据库类似。1.下载模板https://aspnetboilerplate.com/ 根据自己的需求选择版本和前端框架并填写项目名称,点击“Createmyproject!”即可下载一个ABP标准模板项目。  解压下载好的压缩包,找到目录:aspnet-core,接下来就可以用VS打开.sln......
  • 基本数据类型之间的转换
    自动类型转换(隐式转换)自动类型转换发生在从低级类型向高级类型转换时,不需要进行任何显式操作。Java中的基本数据类型按照精度从低到高的顺序是:byte、short、char(在运算中视为int)、int、long、float、double。转换规则如下:精度或可表示范围小的类型自动转换成精度或可表示范围大......
  • C语言---数据类型和变量
    1.数据类型介绍  C语⾔提供了丰富的数据类型来描述⽣活中的各种数据。使⽤整型类型来描述整数,使⽤字符类型来描述字符,使⽤浮点型类型来描述⼩数。所谓“类型”,就是相似的数据所拥有的共同特征,编译器只有知道了数据的类型,才知道怎么操作。2.内置类型1.字符型char  ......
  • C#中常用集合类型
    在C#中,集合是用于存储和操作一组数据项的数据结构。这些集合通常位于System.Collections和System.Collections.Generic命名空间中。下面我将概述C#中几种常用的集合类型及其特点:1.System.Collections命名空间中的集合这个命名空间中的集合类型不支持泛型,因此在编译时不检......
  • 在K8S中,Service的类型有哪几种,请说⼀下他们的用途?
    在Kubernetes(K8s)中,Service是一种抽象,它定义了一组逻辑上相同的服务实例(即Pod)以及访问它们的策略。Service可以将外部客户端的流量路由到后端的一个或多个Pod。Kubernetes提供了几种不同类型的Service,每种都有其特定的用途:ClusterIP描述:这是默认的Service类型。Cl......
  • MySQL数据库——数据库的数据类型(一)
    四、数据类型1.数据类型分类分类数据类型说明数值类型BIT(M)位类型。指定位数,默认值1,范围1-64TINYINT[UNSIGNED]带符号的范围-128127,无符号范围0255.默认有符号BOOL使用0和1表示真和假SMALLINT[UNSIGNED]带符号是-2^15次方到2^15-1,无符号是2^16-1IN......
  • 数据类型
    数据类型强类型语言:要求变量的使用严格符合规定,所有变量必须先定义后才能使用。弱类型语言:要求变量的使用符合规定。JSJava的数据类型分为两大类基本类型数值类boolean类:true和false占一位引用类型类、接口、数组1B(byte、字节)=8bit(位)publicclassDemo3{pu......
  • Linux:文件管理,目录管理,文件类型,链接类型
    1,文件管理用户(标识号:UID):一定资源的使用者,可以创建和管理文件以及访问其他用户文件。可以从属于多个群组。用户组(标识号:GID):由一定数量的对某些文件具有相同操作权限的用户组成的小组。可以拥有多个用户。root用户:超级管理员,可以为所欲为。为何要区分用户和用户组?答:一种功......
  • 基础类型总结
    类型占用byte1字节short2字节char2字节int4字节long8字节double8字节boolean1/8字节float4字节......