微软.NET6开发的C#特性——类、结构体和联合体

我是荔园微风,作为一名在IT界整整25年的老兵,看到不少初学者在学习编程语言的过程中如此的痛苦,我决定做点什么,下面我就重点讲讲微软.NET6开发人员需要知道的C#特性,然后比较其他各种语言进行认识。

C#经历了多年发展, 进行了多次重大创新, 大幅优化了开发者的编码体验。在.NET 平台移交给.NET基金会运营后, C#更新的越来越不像原来的C#了,但总体上来说,所有改进依然以优化开发者的编码体验为最终目的。

首先,要记住一张表,如下:

C#版本     发布时间         .NET版本                       VS版本                  CLR版本

C#1.0        2002-2           .NET Framework 1.0     VS.NET 2002      .NET Framework CLR 1.0

C#2.0        2005-11         .NET Framework 2.0     VS2005               .NET Framework CLR 2.0

C#3.0        2006-11         .NET Framework 3.0     VS2008               .NET Framework CLR 2.0 

C#3.0       2007-11          .NET Framework 3.5     VS2008                .NET Framework CLR 2.0 

C#4.0       2010-4           .NET Framework 4.0     VS2010                .NET Framework CLR 4.0

C#5.0       2012-2           .NET Framework 4.5     VS2012               .NET Framework CLR 4.0

C#6.0       2015-7           .NET Framework 4.6      VS2015              .NET Framework CLR 4.0

C#7.0       2016-8           .NET Framework 4.6.2    VS2017(v15)     .NET Framework CLR 4.0

C#7.1       2017-4           .NET Framework 4.7      VS2017(v15.3)   .NET Framework CLR 4.0

C#7.2       2017-10         .NET Framework 4.7.1   VS2017(v15.5)    .NET Framework CLR 4.0

C#7.3       2018-4           .NET Framework 4.7.2   VS2017(v15.8)   .NET Framework CLR 4.0

C#8.0       2019-4           .NET Framework 4.8    VS2019(v16.3)    .NET Framework CLR 4.0

C#8.0       2019-9           .NETCore 3.0                 VS2019(v16.4)    .NETCore CLR 3.0       

C#9.0       2020-11          .NET 5.0                        VS2019(v16.8)    .NET CLR 5.0           

C#10.0     2021-11          .NET 6.0                        VS2022(v17)       .NET CLR 6.0 

看完这张表,我真的是很感慨,从测试版开始,我居然陪伴着.NET和C#走过了二十多年,我不知道有没有微软公司的人在看这篇文章,如果有的话,不知道我这样的二十多年的.NET和C#程序员有没有机会去微软中国和微软亚洲研究院的总部去参观一下,去坐一坐,并作一下技术交流。二十多年了,人生又有几个二十多年啊。

.NET平台是基于IL中间语言的应用运行环境,面向对象语言C#是平台的主要开发语言。除此之外还有同样面向对象的C++/CLI。C++/CLI主要用于和原生C++交互,在.NET平台中仅支持Windows系统。

C#和.NET平台本来是微软为了与Java平台竞争而打造的,C#在设计时充分总结了Java的经验教训,解决了大量Java的基本设计缺陷。本着为一线开发者谋实惠的宗旨,C#设计了大量能减轻开发者的编写负担、容易理解且安全高效的实用功能。为了尽可能降低因安全措施导致性能大幅下降的影响,C#还在有限的情况下保留了C/C++语言的部分语法和功能。到了.NET时代,微软依然在运行时(Runtime)和语言两边同时进行着优化。

随着上世纪九十年代Java的发布,软件公司和开发者开始感受到基于虚拟机的托管语言所带来的好处,微软也不甘示弱,在2001年发布了.NET Framework平台和C#。提供了完整的基础面向对象支持。

类、结构体和联合体

一、C# 类
1. 什么是类class
class(类),是面向对象中的概念,是面向对象编程的基础。类是对现实生活中一类具有共同特征的事务的抽象。

2. 如何定义实例化一个class
class的实例化:定义一个类后,就必须实例化才能使用。实例化就是创建一个对象的过程。在C#中,使用new关键字来创建。

类 对象 = new 类 () ;

类的声明是以关键字class开始,后跟类的名称组成的。

类的实例是以关键字new开始,后跟类的名称组成的。

3. 类内的变量&函数
在类内声明的非静态变量,称之为 普通成员变量。

在类内声明的变量之前追加static关键字,称之为 静态成员变量。

在类内声明的非静态函数,称之为 普通成员函数。

在类内声明的函数之前追加static关键字,称之为 静态成员函数。

在类的内部声明的静态变量或函数,若想访问,必须通过类名来访问;普通成员变量或函数通过实例化的对象来访问。

4. 构造函数&析构函数
构造函数:

public 类名()

{

​ ……

}

析构函数:

~类名()

{

​ ……

}

// 构造函数   发生在new实例时,会被自动执行。 构造函数可以携带参数,当你显式地编写了自己的构造函数后,系统将不再提供默认的构造函数
// 析构函数   当当前类对象被销毁时,会被自动执行

// 作用域
// 函数内声明的变量(包括类对象),在执行完本函数时,会被自动销毁
// 但是,在类内声明的成员变量(包括类对象),只有当前类被销毁时,那它管理的其他变量(包括类对象)才会被销毁

// 如果不加命名空间,定义的类将在全局命名空间之下
namespace MySpace
{
    public class Myclass
    {
        public int a;
        // 构造函数
        public Myclass()   // 无参构造函数
        {
            Debug.Log("构造函数执行");
        }

        public Myclass(int value)   // 有参构造函数
        {
            a = value;
            Debug.Log("a = ", a);
        }

        // 析构函数
        ~Myclass()
        {
            Debug.Log("析构函数执行");
        }

        public void Show()
        {
            Debug.Log("MShow函数执行");
        }
    }
}

public class function : MonoBehaviour
{
    void Start()
    {
        MySpace.Myclass myclass = new MySpace.Myclass();
        int b = myclass.a;
        myclass.Show();
    }

    void Update()
    {
        // Input 键盘鼠标监听工具类
        if (Input.GetMouseButton(0))  // 0  鼠标左键   1  鼠标右键   2  鼠标滚轮   
        {
            Destroy(this.gameObject);

        }
    }
}

5. 访问修饰符
所谓的访问修饰符,指的是 是否能够访问的到。

Public:任何公有成员可以被外部的类访问。
Private:只有同一个类的函数可以访问它的私有成员。
Protected:该类的内部和继承类可以访问。
internal:同一个程序集(命名空间)的对象可以访问。
Protected internal:3和4的并集,符合任意一条可以访问。


范围比较:

private < internal/protected < protected internal < public

protected限定的是只有在继承的子类中才可以访问,可以跨程序集 ;

internal限定的是只有在同一个程序集中才可以访问,可以跨类 。

二、C#面向对象
1. 继承
继承是面向对象程序设计中最重要的概念之一,继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得容易。同时也有利于重用代码和节省开发时间。

当创建一个类时,不需要完全重写新的成员变量和成员函数,只需要设计一个新的类,继承已有的类的成员即可。这个已有的类被称为 基类,这个新的类被称为 派生类。

C#中用“ : ”表示继承。C#不支持多重继承。

继承,就是将 共用的属性或方法抽离到基类 的过程,这个思维称之为面向对象。

2. 封装
封装,被定义为“把一个或多个项目封闭在一个物理的或逻辑的包中”。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

封装,是将实现细节通过接口的方式暴露给第三方,而不需要关心实现细节。

封装和抽象是相辅相成的,抽象允许相关信息可视化,而封装则是使开发者实现所需级别的抽象。

C#的封装根据具体的需要,设置使用者的权限,并通过 访问修饰符 来实现。

访问修饰符

Public:任何公有成员可以被外部的类访问。
Private:只有同一个类的函数可以访问它的私有成员。
Protected:该类的内部和继承类可以访问。
internal:同一个程序集(命名空间)的对象可以访问。
Protected internal:3和4的并集,符合任意一条可以访问。
范围比较:

private < internal/protected < protected internal < public

虚函数
virtual代表虚函数,意味着 子类可以覆盖其实现,如果子类不覆盖,那将使用父类的同名函数。

子类使用override重写虚函数。

3. 多态
多态,是同一个行为具有多个不同表现形式或形态的能力;多态,就是同一个接口,使用不同的实例而执行不同操作。

静态多态(编译时)

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C#提供了两种技术来实现静态多态性。分别为:函数重载和运算符重载。

动态多态(运行时)

在运行时,根据实例对象,执行同一个函数的不同行为。

运行时多态,在运行前无法确认调用哪个方法,只有在运行时才能确定的方法,这种行为称之为动态多态。

具体实现为,将派生类实例化对象赋给基类实例化的对象,用后者调用继承的方法。

4. 重载和覆盖
覆盖,发生在继承关系中,通过virtual和override实现,函数名和函数参数一模一样;

重载,发生在任何关系中,只要保证函数名字一致,参数不一致(带参数和不带参数,带参数顺序不一致,或者参数个数不一致),即可实现重载。

5. this和base关键字
this:可访问当前类能访问到的属性和方法;

base:只能访问基类的属性和方法。

结构体Struct
用 struct + name {…………} 声明一个结构体。

结构体是值类型数据结构,它使得一个单一变量可以存储各种数据类型的相关数据。

结构体的特点:

结构体可带有方法、字段、索引、属性、运算符方法和事件;
结构体可定义构造函数,但不能定义析构函数、不能定义无参构造函数。无参构造函数是自动定义的,且不能被改变;
与类不同,结构体不能继承其他结构体或类,但是可以实现一个或多个接口;
结构体不能作为其他结构体或类的基础结构;
结构成员不能指定为abstract、virtual 或 protected;
结构体不用通过 new 来实例化。
struct和class的异同

相同点
1. 都支持静态构造函数、有参构造函数;
2. 都支持自定义函数;
3. 结构体和类对于const修饰的变量的使用方式是一样的。

不同点
1. 构造函数:结构体 不允许定义无参构造函数,只允许定义有参构造函数,但是类可以;
2. 析构函数:结构体不允许定义析构函数,但类可以;
3. 函数修饰符:结构体函数不允许声明为virtual、protected,但是类可以;
4. 类型修饰符:结构体类型不允许声明为abstract,但是类可以;
5. 关于变量
(1)普通变量
结构体声明的全局普通变量(不带修饰符),不能在声明时直接赋值,只能在构造函数里赋值,但是类都可以;
(2)readonly类型的变量
结构体声明的全局readonly变量,只能在构造函数里赋值,而类都可以。
6. 关于继承
结构体之间不可以互相继承,但是类与类之间是可以继承的(sealed密封类除外)。
7. 在使用上
​ (1)访问变量
​ 结构体访问成员变量,给变量显式赋值,就可直接访问;而类必须实例化后才能访问;
​ 结构体如果不通过new初始化,是不可以直接访问其内部变量的(const除外)。
​ (2)访问函数
​ 结构体变量和类对象 必须进行初始化,才可以访问
8. new
(1)结构体属于值类型,结构体的new,并不会在堆上分配内存,仅仅是调用结构体的构造函数初始化而已;
(2)类属于引用类型,类的new,会在堆上分配内存,而且也会调用类的构造函数。

类和结构体是从C/C++继承的功能,结构体从C语言开始就作为供开发者自定义数据结构的基本功能出现,在C++中升级为了面向对象的类。C++的类和结构体并没有明确的概念和功能上的差别,Java删除了结构体这个概念,只保留了类。微软发现了能够有效利用这两个概念的方法,因此C#保留了类和结构体。

Java和C#都有一套完善的类型系统,所有的类型都是直接或间接地由 Object派生而来。但不知为何Java有基元类型。基元类型基本代表了在C/C++中由编译器和CPU直接支持的原始类型,而这些类型却不属于类型系统。为了解决这个问题,Java又设计了一套包装类型。这带来一个问题,类型系统是Java的根基,但作为其中的基石的基元类型居然和类型系统不兼容,这也在后来为Java带来了更多的麻烦。

C#却巧妙地利用了类和结构体完成了没有内生矛盾的类型系统,一切类型都是Object的后代,包括基本数据类型。在C#的设计中,基本类型的继承路径是 System.Object→System.ValueType→各种基本类型。

ValueType禁止使用常规语法继承,并且其子类是强制封闭的,禁止继续继承。这时结构体就派上用场了,结构体就是隐式继承了ValueType的封闭类型,基本数据类型就是由.NET预定义的结构体。结构体是直接在线程栈上分配的免回收类型,拥有极高的性能。也因为在栈上的分配必须静态确定其占用的内存空间并直接分配,因此结构体禁止赋值为null。

由于结构体的复制策略是深拷贝,因此在方法之间作为参数传递时传递的是完整的独立副本,互不影响,和普通类的引用拷贝形成了鲜明的对比。为了保持和Object的完整兼容性,.NET还特地为结构体准备了自动装箱和拆箱。装箱即是指在显式或隐式转换为Object类型的时候运行时自动在托管堆上分配对象内存并把值复制到对象中;拆箱即是指在显式或隐式转换回原类型时自动在线程栈上分配内存并把值从托管堆复制到栈上。托管堆中的对象占用的内存一般比线程栈上的大,因为堆对象占用的内存除了基本值所需内存之外还包含类型对象指针和同步块索引(C#的lock同步锁语句块就是依靠同步块索引实现的)等额外信息,而栈上的结构体实例只占用基本值所需的内存。经过这些周密的设计,C#拥有了完美自治的类型系统,类和结构体也拥有了明显的功能区分。

在C++中有一个被称为友元类的功能,友元类之间允许相互访问对方的私有成员,这在一定程度上破坏了类的封装性,因此Java和C#都删除了这个功能。不过C#却有一个被称为友元程序集的功能,友元程序集的类之间允许互相访问对方的内部(internal)成员,这在编写单元测试时经常用到。由于C#和Java都是托管语言,因此都可以通过反射彻底绕过成员访问保护机制,C/C++也可以通过万能的指针绕过编译器保护。

C#、C、C++、Java的类和结构体的代码如下。

(1)C#

//结构体
public struct Point2D
{
  public double x;
  public double y;
}

//类
public class Point3D
 {
  public double x;
   public double y;
   public double z; 
} 
 
//抽象类 
public abstract class MyClass 
{ 
    public abstract void Method1(); 
    public virtual void Method2() {} 
} 

(2)C

typedef struct {
   double x; 
   double y; 
} Point 2D; 

(3)C++

① Point3D.h

#pragma once 
 
class Point3D 
{ 
    public: 
        double x; 
        double y; 
        double z; 
}; 

class MyClass 
{ 
     public: 
         virtual void Method1() =0; 
         virtual void Method2();
};

② Point3D.cpp

#include"Point3D.h" 

void MyClass::Method2() {} 

(4) Java

public class Point3D { 
    public double x; 
    public double y; 
    public double z; 
}

public abstract class MyClass{ 
    public abstract void method1(); 
    public void method2() {}
} 

在C语言中有一种独特的数据结构叫作联合体,它的特点是所有数据成员共享内存空间,并且同一时刻最多只有一个成员处于可用状态。这种数据类型的诞生主要是因为C语言刚面世的时候计算机的内存还比较小, 需要尽量节约使用内存。C#和Java诞生的年代内存不再紧缺, 因此并不支持这种数据类型。但是C#为了兼容和C语言的互操作, 从.NET Framework 1.1开始支持通过特殊方式模拟联合体。 

C#在模拟联合体时,如果其中有类类型的成员,可能在运行时引发异常,因此在模拟联合体时一般只用结构体类型的成员,当然,虽然读取非激活状态的成员不会引发异常,但是仍然可能读取到错误的值。在C语言的联合体中通常也只使用基本数据类型。

C的联合体和C#的模拟联合体示例代码分别如下所示。

(1) C

typedef union {
   int x;
    float y;
   double z;
} MyUnion;


(2)C#

using System. Runtime.InteropServices;

namespace Example
{
  //手动定义结构体布局,占用8字节空间
  [StructLayout (LayoutKind. Explicit, Size =8)]
  public struct MyUnion
   {
    //字段偏移量为0,实际占用4字节,剩下4字节不使用
       [Fieldoffset(0)]
      public int x;

     //字段偏移量为0,实际占用4字节,剩下4字节不使用
      [Fieldoffset(0)]
      public float y;
 
     //字段偏移量为0,刚好用完8个字节
     [Fieldoffset (0)]
     public double z;
    }
}

C#使用结构体模拟联合体时,StructLayout特性可以告知运行时(Runtime)开发者要手动定义结构体的内存布局,其中字段的FieldOffset特性告知运行时这个字段在对象中的内存偏移量,示例中全部指定为0就表示所有成员共享相同的内存空间。上面代码中的各个字段所需的内存空间不尽相同,因此当需求内存较小的成员处于激活状态时,多余的内存会处于空闲状态。
 

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

  • 14
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值