C#从入门到入坟(原创不易,转载请注明出处)

news2024/11/28 12:40:21

文章目录

  • C# 基础篇
  • 0 环境部署
  • 1 Hello world
    • 1.1 两种框架
    • 1.2 创建项目的过程
    • 1.3 项目组成结构
      • 1.3.1 解决方案和项目
      • 1.3.2 程序集信息
      • 1.3.3 引用
      • 1.3.4 配置文件
      • 1.3.5 程序入口类
    • 1.4 解决方案
    • 1.5 Debug 和 Release
    • 1.6 CSharp虚拟机初始
    • 1.7 .Net Framework混合语言开发
    • 1.8 托管代码和非托管代码
    • 1.9 公共语言运行时CLR
  • 2 编程要素
    • 2.1 项目组成
    • 2.2 命名空间和类
    • 2.3 数据类型
    • 2.4 变量
    • 2.5 注释
    • 2.6 当前阶段常见错误
      • 2.6.1 命名空间和类名错误
      • 2.6.2 变量错误
  • 3 输入输出
    • 3.1 Write WriteLine
    • 3.2 Read ReadLine
    • 3.3 字符串格式化方法
  • 4 运算符
    • 4.1 赋值运算符
    • 4.2 算数运算符
    • 4.3 比较运算符
  • 5 类型转换
    • 5.1 值类型之间的转换
    • 5.2 值类型与字符串类型之间的转换
    • 5.3 万能转换器Convert
  • 6 程序控制语句
    • 6.1 条件选择 if
      • 6.1.1 if 语句
      • 6.1.2 if-else语句
      • 6.1.3 if-else if -else 语句
    • 6.2 混插-关于优先级
    • 6.3 三元 运算符
    • 6.4 switch
    • 6.5 for 循环
    • 6.6 while 循环
    • 6.7 do...while 循环
  • 7 字符串
    • 7.1 字符串的常用方法
    • 7.2 字符串格式化
    • 7.3 空值&空对象
      • 7.3.1 空值
      • 7.3.2 空对象
    • 7.4 高效处理字符串
    • 7.5 转义字符
    • 7.6 字符串分割与连接
  • 8 数组
    • 8.1 基本概念
    • 8.2 数组遍历
    • 8.3 值类型与引用类型
    • 8.4 底层类和关键字
  • 9 常量枚举
    • 9.1 常量
    • 9.2 枚举
  • 面向对象Basic
  • 10 类与对象
    • 10.1 概念
    • 10.2 属性Property
      • 10.2.1 创建一个类,并实例化
      • 10.2.2 自动属性
      • 10.2.3 属性与成员变量(字段)的比较
      • 10.2.4 属性的新特性 (>4.6)
      • 10.2.5 类属性也是一种类型的场景
    • 10.3 类的访问修饰符
    • 10.4 方法
      • 10.4.1 构造方法
      • 10.4.2 类初始化的步骤
      • 10.4.3 this关键字在类中的两个作用
      • 10.4.4 对象初始化器
      • 10.4.5 实例方法
      • 10.4.6 静态方法
      • 10.4.7 不确定参数
      • 10.4.8 垃圾回收机制
  • 11 泛型集合
    • 11.1 集合类型
    • 11.2 字典类型
  • CSharp高级进阶
  • 1 OOP - 继承 inherit
    • 1.1 关键字base this
    • 1.2 protected关键字
    • 1.3 private 关键字
    • 1.4 abstract关键字,抽象方法
  • 2 OOP-多态
    • 2.1 继承多态
  • 3 虚方法
  • 4 new关键字
  • 5 接口
    • 5.1 面向接口编程
    • 5.2 创建接口
    • 5.3 创建接口实现类
    • 5.4 基于接口实现多态
  • 6 反射
    • 6.1 概念
    • 6.2 实现反射
  • 7 泛型
    • 7.1 List和Dictionary回顾
    • 7.2 引入泛型
    • 7.3 泛型类、泛型方法、泛型返回值
    • 7.4 default关键字
    • 7.5 dynamic关键字
    • 7.6 泛型约束
  • 8 委托
    • 8.1 引入委托
    • 8.2 使用委托的步骤
    • 8.3 委托的基本应用
    • 8.4 匿名方法
    • 8.5 lambda表达式
    • 8.6 自定义泛型委托

C# 基础篇

0 环境部署

安装Visual Studio。

下载地址:https://visualstudio.microsoft.com/zh-hans/

可以选择社区版本,是可以免费使用的。

下载之后配置安装。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dmsohRgO-1684053040564)(.\CSharp.assets\image-20230303110318431.png)]

按照自己的工作需要,勾选相应的组件和安装位置,进行安装即可。

1 Hello world

1.1 两种框架

目前C#开发的两种框架

运行于windows的.Net Framework

可以跨平台的.Net6

1.2 创建项目的过程

  • 项目名称
    • 建议:英文命名(公司名称.项目名称)
  • 解决方案名称
    • 可以和项目名称一样
  • 框架版本
    • 建议4.6以上(框架版本和VS版本是对应的)

创建一个新项目

  • 打开VS,选择创建新项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDp84Tyu-1684053040565)(.\CSharp.assets\image-20230303111623293.png)]

  • 选择编程语言

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpHhBotP-1684053040565)(.\CSharp.assets\image-20230303111707588.png)]

  • 选择运行平台

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvCbX0cd-1684053040566)(.\CSharp.assets\image-20230303111726401.png)]

  • 选择程序类型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C7GbX29d-1684053040567)(.\CSharp.assets\image-20230303111814780.png)]

web、服务、控制台、库、桌面用的比较多,初学阶段使用控制台程序。

创建一个新项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJTdyu06-1684053040568)(.\CSharp.assets\image-20230303112029074.png)]

至此一个新项目创建完成

编写hello world

// 系统默认引用的命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 定义命名空间
namespace ZJF.SayHi
{	
    // 定义类
    internal class Program
    {	
        // Main方法是程序的唯一入口
        static void Main(string[] args)
        {   
            // 输出Hello world
            Console.WriteLine("Hello C# world! ");
            // 保持输入
            Console.ReadLine();
        }
    }
}

1.3 项目组成结构

1.3.1 解决方案和项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcvSJtDB-1684053040568)(.\CSharp.assets\image-20230303113021129.png)]

解决方案和项目是一对多的关系,一个解决方案下面可以有至少一个或者多个项目,我的理解可能是比较大的系统,分模块开发的过程

1.3.2 程序集信息

程序集信息描述了项目的元信息。比如版权信息,版本信息,程序集名称等。

程序集信息可以从两个位置看到

1 项目文件夹下面的Properties

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("ZJF.SayHi")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ZJF.SayHi")]
[assembly: AssemblyCopyright("Copyright ©  2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]

// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("fa954032-1d23-4d86-adc3-64505c285241")]

// 程序集的版本信息由下列四个值组成: 
//
//      主版本
//      次版本
//      生成号
//      修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

2 鼠标右键项目名选择属性,也可以通过界面修改程序集信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIHYELog-1684053040569)(.\CSharp.assets\image-20230303113315122.png)]

1.3.3 引用

引用自己封装的模块或者是第三方的模块,也叫程序集

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypyl25TQ-1684053040569)(.\CSharp.assets\image-20230303113606449.png)]

1.3.4 配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UiH7jtZc-1684053040570)(.\CSharp.assets\image-20230303113732925.png)]

每个项目有且只能有一个,而且不能改名字!

1.3.5 程序入口类

每个项目必须有且只能有一个程序入口,程序入口类必须有一个Main的静态方法。

1.4 解决方案

解决方案就是一系列程序模块的组合,解决方案可以统一编译,同一更新。

生成解决方案:生成exe可执行文件

重新生成解决方案:删除已经生成的可执行文件,重新生成

清除解决方案:清理掉解决方案

1.5 Debug 和 Release

Debug

调试模式,可以打断点,观察程序的执行过程

Release

把已经调试好的程序发布,Release后的程序更加轻量级,编译器内部做了优化

1.6 CSharp虚拟机初始

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4L8a6oGa-1684053040570)(.\CSharp.assets\image-20230303114521823.png)]

1.7 .Net Framework混合语言开发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtgiWaqA-1684053040575)(.\CSharp.assets\image-20230303114616743.png)]

1.8 托管代码和非托管代码

托管代码

在.Net 平台下面写的程序, C# , java

非托管代码

编译之后,直接运行在OS中, C

1.9 公共语言运行时CLR

CLS : 公共语言规范 ,解决不同语言之间的语法问题

CTS: 通用类型系统,解决不同开发语言之间的数据类型差异

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8GyDNiI-1684053040575)(.\CSharp.assets\image-20230303115001017.png)]

2 编程要素

2.1 项目组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1yggheT-1684053040576)(.\CSharp.assets\image-20230303115126667.png)]

2.2 命名空间和类

在面向对象编程语言中,类是程序的基本单元,用类可以封装要处理的数据(字段、属性)、和具体的处理过程(方法)

一个C#程序的抽象

引入命名空间

namespace 命名空间名称  
{
    class 类名称
    {
        字段 属性
        构造方法
        方法
        public 返回值 方法名称(参数)
        {
            // 方法具体代码
        }
    }
}
// 引入的命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 当前类所在的命名空间
namespace ZJF.SayHi
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello C# world! ");
            Console.ReadLine();
        }
    }
}

命名空间原则

  • **必须要有:**任何一个类都要属于一个特定的命名空间
  • **用于管理:**通过命名空间更好的对“类”进行分类管理
  • **自己规划:**一个项目可以根据实际需要,开发者自己划分若干名字空间
  • **默认名称:**默认命名空间和项目名称相同
  • **使用方法:**用”.“分割,但是不表示包含关系,是层次关系
  • **特别注意:**不能以数字、特殊符号开头,不建议用中文

命名空间引入时间

  • 两个类所在的命名空间不同
    • 其中一个类使用另一个类,则要引入命名空间或者全限定名
  • 两个类所在的类库不同
    • 先引入类库,在引入命名空间

示例代码一:引用不同命名空间中的类的使用场景

  • 关于默认命名空间

    默认的命名空间是项目名称,可以通过鼠标右键,选择属性,修改默认的命名空间,要根据实际的项目情况,规划命名空间,命名空间可以让程序更有层次感,结构更清晰。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAWkPcdd-1684053040576)(.\CSharp.assets\image-20230303123948756.png)]

    当然,我们也可以在程序源代码直接手动修改命名空间。

  • 项目名/program.cs 这是入口文件

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.SayHi
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                TestClass testClass = new TestClass();
                testClass.TestID = 1;
                testClass.TestName = "test";
    
                TestClass2 testClass2 = new TestClass2();
                testClass2.TestID = 2;
                testClass2.TestName = "test";
            }
        }
    }
    
    
  • 项目名/TestClass1.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.SayHi
    {
        class TestClass
        {
            public string TestName { get; set; }
            public int TestID { get; set; }
        }
    }
    
    
  • 项目名/TestClass2.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.SayHi.Test2
    {
        internal class TestClass2
        {
            public string TestName { get; set; }
            public int TestID { get; set; }
        }
    }
    
    

在这个示例中,TestClass1与Program具有相同的命名空间,TestClass2具有不同的命名空间,如果直接使用TestClass2会直接编译错误:

未找到类型或者命名空间,提醒使用using引入

如果需要使用TestClass2,那么需要在Program中添加命名空间的引用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

#region 处理命名空间引入错误
using ZJF.SayHi.Test2;
#endregion

namespace ZJF.SayHi
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TestClass testClass = new TestClass();
            testClass.TestID = 1;
            testClass.TestName = "test";

            TestClass2 testClass2 = new TestClass2();
            testClass2.TestID = 2;
            testClass2.TestName = "test";
        }
    }
}

示例代码2:不同的命名空间中有相同名称的类,使用完全限定名

不同的命名空间中,允许有相同名称的类型,但是在其他的命名空间中引用的时候,只能使用全限定名。

全限定名=命名空间.类名

  • 项目名/TestClass2.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.SayHi.Test2
    {
        internal class TestClass2
        {
            public string TestName { get; set; }
            public int TestID { get; set; }
        }
    }
    
    
  • 项目名/TestClass3.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.SayHi.Test3
    {
        internal class TestClass2
        {
            public string TestName { get; set; }
            public int TestID { get; set; }
    
            public void SayHello()
            {
                Console.WriteLine($"{this.TestName} say hello");
            }
        }
    }
    
    
  • 项目名/Program.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using ZJF.SayHi.Test2;
    using ZJF.SayHi.Test3;
    
    
    namespace ZJF.SayHi
    {
        internal class Program
        {
            static void Main(string[] args)
            {
    
                TestClass2 testClass2 = new TestClass2();  
                testClass2.TestID = 2;
                testClass2.TestName = "test";
                
                TestClass2 testClass3 = new TestClass2();
                testClass3.TestID = 3;
                testClass3.TestName = "test";
    
            }
        }
    }
    
    

同时引用两个不同的命名空间,分别是ZJF.SayHi.Test2和ZJF.SayHi.Test3,这两个命名空间中具有相同的类TestClass2。

在Program中引用之后,会出现报错:TestClass2不存在明确的引用。

  • 解决方案(使用全限定名)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
    
    namespace ZJF.SayHi
    {
        internal class Program
        {
            static void Main(string[] args)
            {
    			// 修改成全限定名 
                ZJF.SayHi.Test2.TestClass2 testClass2 = new ZJF.SayHi.Test2.TestClass2();
                testClass2.TestID = 2;
                testClass2.TestName = "test";
    
                ZJF.SayHi.Test3.TestClass2 testClass3 = new ZJF.SayHi.Test3.TestClass2();
                testClass3.TestID = 3;
                testClass3.TestName = "test";
    
            }
        }
    }
    
    

全限定名,除能够处理这种不同命名空间相同类名情况,后面还会用于反射中!

2.3 数据类型

  • 数值型

    • 整数
      • byte 无符号 1个字节
      • short 有符号 2个字节
      • int 有符号 4个字节
      • long 有符号 8个字节
      • sbyte 有符号1个字节
      • ushort 无符号2字节
      • uint 无符号4字节
      • ulong 无符号8字节
    • 浮点数
      • float 4字节 7位小数 默认是double 如果定义float需要在后面加上一个f,大写的F和小写的f都可以
      • double 8字节 16位小数
  • 非数值型

    • 字符

      • 单个字符用单引号表示
    • 字符串

      • string
    • 布尔

      • bool
    • 日期

      • DateTime

        DateTime dt = Convert.ToDateTime("2022-1-1")
        
    • 数组

    • 集合

    • 对象

2.4 变量

表示一个数据的存储空间,地址的别名,其实是C语言中的指针,存储变量地址的变量,只是C#弱化了指针的概念。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wCPbnTy-1684053040577)(.\CSharp.assets\image-20230303132333187.png)]

变量三要素

  • 数据类型
  • 变量名称
  • 变量的值

变量使用方法

  • 定义(声明) :规定变量的类型和名称
  • 赋值 通过赋值运算符给已声明的变量赋值
  • 读取 获取变量的值

变量命名规范

  • 组成:英文字母数字下划线
  • 开头:只能以字母或者下划线开头
  • 禁用:关键字和$
  • 名称:见明知意
  • 区分:严格区分大小写
  • 原则:使用Camel命名法,首字母小写,其他单词首字母大写

类、属性、方法命名

  • 原则:Pascal命名法,首字母大写,其他单词字母也大写
  • 举例:PersonInfo(类名) PersonID(属性) ShowInfo(方法)

2.5 注释

  • 当行注释

    // 注释
    
  • 文档注释

    namespace ZJF.DataTypeAndVar
    {
        internal class Program
        {
            /// <summary>
            /// 入口静态方法
            /// </summary>
            /// <param name="args"></param>
            static void Main(string[] args)
            {
            }
        }
    }
    
    

类名和方法名和程序上面一定要加一些注释,方便自己理解或者团队开发者理解。

2.6 当前阶段常见错误

2.6.1 命名空间和类名错误

  • 命名空间或者类名写错,没有明确区分大小写
  • 类中嵌套类
  • 没有引入类库

示例代码,在同一个解决方案中创建一个类库,并引用该类库

右键解决方案,添加新项目。创建一个新的类库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pvaxLd6E-1684053040577)(.\CSharp.assets\image-20230303135805406.png)]

类库的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//类库中Class1类所在的命名空间是TestDLL
namespace TestDLL 
{
    public class Class1
    {
    }
}

创建成功之后在解决方案下面有两个项目,分别是类库和一个控制台项目。

在控制台项目中使用类库中的类,需要两个步骤:

1 引用类库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rH5hqHLh-1684053040578)(.\CSharp.assets\image-20230303140001275.png)]

2 引入命名空间

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 使用类库的命名空间
using TestDLL;

namespace ZJF.DataTypeAndVar
{
    internal class Program
    {
        /// <summary>
        /// 入口静态方法
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)

        {
            // 实例化类库中的类
            Class1 class1 = new Class1();
        }
    }
}

2.6.2 变量错误

  • 变量位声明,先使用
  • 变量未赋值,先使用
  • 变量名非法,关键字、大小写、特殊字符

3 输入输出

3.1 Write WriteLine

 static void Test1()
        {
            Console.WriteLine("今天天气怎么样?");
            Console.Write("天气很好呀");
            Console.Write("有什么计划吗?\n");
            Console.WriteLine("去郊游那");
        }

输出:

今天天气怎么样?
天气很好呀有什么计划吗?
去郊游那

Console.WriteLine 等价于 Console.Write + “\n”

3.2 Read ReadLine

从控制台输入的字符默认是字符串类型,可以通过字符串接收信息。

static void Test2() {
            Console.WriteLine("欢迎使用信息录入系统");
            Console.WriteLine("请输入姓名:");
            string name = Console.ReadLine();
            Console.WriteLine("请输入电话:");
            string phone = Console.ReadLine();
            
            Console.WriteLine("姓名:"+name+"电话: "+phone);
        }

输出:

欢迎使用信息录入系统
请输入姓名:
zhansan
请输入电话:
1231
姓名:zhansan电话: 1231

Console.Read 和 Console.ReadLine于Write和Writeline相同

3.3 字符串格式化方法

static void Test3()
        {
            string name = "kobe";
            int age = 41;
            string hobby = "打篮球";

            // 直接拼接

            string result1 = "姓名:" + name + "年龄:" + age + "爱好:" + hobby;

            // 格式化方式


            // 使用$符号格式化
            string result3 = $"姓名:{name},年龄:{age},爱好:{hobby}";

            Console.WriteLine(result1);
            Console.WriteLine("姓名:{0},年龄:{1},爱好:{2}", name, age, hobby);
            Console.WriteLine(result3);
        }

最常用的是使用$符号。

4 运算符

4.1 赋值运算符

int a = 1; 这是一个赋值运算符操作

4.2 算数运算符

static void Test4() {
            // 赋值运算符
            int a = 10;
            int b = 20;

            // + 
            Console.WriteLine($"a+b={a+b}");

            // - 
            Console.WriteLine($"a-b={a - b}");

            // *
            Console.WriteLine($"a*b={a * b}");

            // / 
            Console.WriteLine($"a/b={a/b}");

            // % 求模
            Console.WriteLine($"a%b={a%b}");

            // +=  -= *= /= %=
            Console.WriteLine($"a+=40={a+=40}");

            // ++ -- 
            Console.WriteLine($"a++={a++}");

            Console.WriteLine($"a--={a--}");

            Console.WriteLine($"--a={--a}");

            Console.WriteLine($"++a={++a}");


        }

输出结果:

a+b=30
a-b=-10
a*b=200
a/b=0
a%b=10
a+=40=50
a++=50
a–=51
–a=49
++a=50

其他的算术运算符都比较好理解

主要理解一下++ –

++ – 在变量的后面是先输出在进行加减1操作

相反,如果在变量的前面是先加减1操作,然后再进行输出

4.3 比较运算符

大于、小于、等于、大于等于、小于等于

比较运算符一般用于值类型,string被当作值类型使用。

5 类型转换

5.1 值类型之间的转换

自动类型转换

目标类型 > 使用的数据类型

从int转到double,自动类型转换,小范围的类型可以安全的放在大范围的类型。

举个例子:

本来一个装大象的冰箱,现在用这个冰箱装猴子,肯定是可以装下的。

强制类型转换

目标类型 < 使用的数据类型

大范围的数据类型转换成小范围的数据类型。

显示类型转换 int result =(int)a + b;

引用类型需要先转换成浮点类型,然后在转换成整数类型。

代码

  • 自动类型转换

    static void Test1() { 
                int a = 100;
                double b = a;
    
                Console.WriteLine($"a={a},b={b}");
            }
    

    输出:a=100,b=100

    小类型的int转换成大类型的double,是自动转换过来的。

  • 强制类型转换

    static void Test2()
            {
                double a = 100;
                int b = a;
                Console.WriteLine($"a={a},b={b}");
            }
    

    直接报错:无法将类型“double”隐式转换为“int”。存在一个显式转换

    static void Test2()
            {
                double a = 100;
                int b = (int)a; // 将double类型强制转换成int类型,丢失部分精度
                Console.WriteLine($"a={a},b={b}");
            }
    

    输出:a=100,b=100

5.2 值类型与字符串类型之间的转换

理解类型的有效表示形式

如果用一个字符串表示整数类型的值:那么“100”就是整数类型的有效表示形式,“100.5”就不是整数类型的有效表示形式,而是浮点数类型的有效表示形式。

代码

  • 字符串转换成值类型

    static void Test3()
            {
                string a = "100";
                string b = "100.5";
                int c = int.Parse(a);
                double d = double.Parse(b);
                Console.WriteLine($"c={c},d={d}");
            }
    

    输出:c=100,d=100.5

  • 如果将整数表达形式的字符串转换成浮点数表达形式的字符串,会直接报错

    static void Test4()
            {
                string a = "100.5";
                int b= int.Parse(a); // 输入的字符串格式不正确
                Console.WriteLine(b);
            }
    

    使用两次转换解决这个问题

    static void Test5()
            {
                string a = "100.6";
                int b = (int)double.Parse(a);
                Console.WriteLine(b);
            }
    

    输出100

  • 值类型转换成字符串类型 ToString

     static void Test6() {
                double a = 100.8;
                Console.WriteLine(a.ToString()); //100.8
            }
    

5.3 万能转换器Convert

万能转换器同样要求转换的内容是目标类型的有效表示形式

代码

static void Test7()
        {
            int a = Convert.ToInt32("1");
            double b = Convert.ToDouble("100.89");
            string c = Convert.ToString(1111);
            Console.WriteLine($"a={a},b={b},c={c}");
        }

6 程序控制语句

6.1 条件选择 if

使用条件选择一定要知道,逻辑运算符:

  • && 逻辑与
  • || 逻辑或
  • ! 逻辑非

6.1.1 if 语句

语法

if(condition){
    // 条件体
}

实际应用

假如有一个网站,需要满足18岁之后才能够观看,可以使用if语句进行测试

static void Test1() {
            Console.WriteLine("请输入年龄:");
            int age = int.Parse(Console.ReadLine());
            if (age >= 18) {
                Console.WriteLine("欢迎观看");
                return;
            }
            Console.WriteLine("您无法观看此网页");
            return;
        }

6.1.2 if-else语句

语法

if(condition){
	// 条件体 	
}else{
	// 条件体
}

实际应用

假如考试成绩超过80分,就合格,发奖状,否则不合格,叫家长。

 static void Test2()
        {
            Console.WriteLine("请输入分数:");
            int score = int.Parse(Console.ReadLine());
            if (score >= 80)
            {
                Console.WriteLine("考试合格,发奖状!");
            }
            else {
                Console.WriteLine("考试不合格,叫家长!");
            }

        }

6.1.3 if-else if -else 语句

对考试成绩进行等级划分,分为A,B,C,D四个等级。

 static void Test3()
        {
            Console.WriteLine("请输入分数:");
            int score = int.Parse(Console.ReadLine());
            char grade;
            if (score < 60)
            {
                grade = 'D';
            }
            else if (score < 70)
            {
                grade = 'C';
            }
            else if (score < 80)
            {
                grade = 'B';
            }
            else if (score < 100)
            {
                grade = 'A';
            }
            else

            {
                grade = ' ';
                Console.WriteLine("输入不合法!");
            }

            Console.WriteLine($"本次考试的成绩等级为:{grade}");

        }

6.2 混插-关于优先级

对于运算符比较多的时候,用小括号控制优先级

优先级排队

  1. 最高优先级:小括号()
  2. 最低优先级:赋值 =
  3. 优先级排序:!> 算术运算符 > 比较运算符 > && > ||

6.3 三元 运算符

对if…else…结构的简单写法

语法

condition ? 为真的表达式 : 为假的表达式

代码测试

static void Test5() {
            int a = 1;
            int b = 2;
            int max = a > b ? a : b;
            Console.WriteLine($"较大值:{max}");
        }

6.4 switch

switch与多重if的比较

  • 相同点

    都是用来处理多分支条件的结构

  • 不同点

    1. 多重if没有switch选择结构的限制,特别适用于某个变量处于某个连续区间的情况
    2. switch只能处理等值条件判断的情况,而且条件必须是值类型或者字符串类型,字符串类型被当作值类型使用
    3. switch做等值条件判断,更加高效。

switch 语法

switch(type){
	case value:{
		// 语句
		// break;
	}
	case value:{
		// 语句
		// break;
	}
	default:{
		// 语句
		// break;
	}
}

代码

static void Test6()
        {
            string choice = Console.ReadLine();
            switch (choice)
            {
                case "A":
                    {
                        Console.WriteLine("选择A");
                        break;
                    }
                case "B":
                    {

                        Console.WriteLine("选择B");
                        break;
                    }
                case "C":
                    {

                        Console.WriteLine("选择B");
                        break;
                    }
                case "D":
                    {

                        Console.WriteLine("选择B");
                        break;
                    }
                default: {

                        Console.WriteLine("不合法");
                        break;
                    }
            }

6.5 for 循环

固定循环次数使用for

代码

  • 使用for循环计算1-100的累加和

    static void Test2()
            {
                int sum = 0;
                for (int i = 0; i <= 100; i++)
                {
                    sum += i;
                }
                Console.WriteLine(sum);
            }
    

    输出为:5050

  • 双层循环

    假设有一个场景, 我们给三个人测量身高,为保证测量的准确性,我们每个人测试三次,求平均值,最终输出身高最高那个人的身高值。

    static void Test3()
            {
                double[] avgArr = new double[3];
                for (int i = 0; i < 3; i++)
                {
                    double avg, total = 0;
                    for (int j = 0; j < 3; j++)
                    {
                        Console.WriteLine($"请输入第{j}个人的身高:");
                        double h = double.Parse(Console.ReadLine());
                        total += h;
                    }
                    avg = total / 3;
                    Console.WriteLine($"第{i}个人的平均身高:{avg}");
                    avgArr[i] = avg;
                }
                double max = 0;
                foreach (var item in avgArr)
                {
                    if (item > max)
                    {
                        max = item;                        }
                }
                Console.WriteLine($"身高最大值:{max}");
            }
    

6.6 while 循环

while 循环适用于不固定循环次数的场景。

代码

  • 通过flag确定循环的启动和停止

    while (flag) {
    // 循环体
    }
    

6.7 do…while 循环

do…while 无论怎样至少执行一次循环,while循环条件判断失败一次都不会执行。

7 字符串

7.1 字符串的常用方法

IndexOf,返回字符串中一个字符的索引位置,找不到返回-1

注意: 索引的初始位置是0.

  • 使用场景:截取字符串的某个部分,配合Substring方法
static void Test4() {
            string email = "1032141242135@qq.com";
            int a = email.IndexOf('@');
            int b = email.IndexOf("qq.com");

            Console.WriteLine($"@ index is {a}; qq.com index is {b}"); // @ index is 13; qq.com index is 14
        }

Length,返回字符串的长度

注意:空格也是一个字符

static void Test4() {
            string email = "1032141242135@qq.com";
            int a = email.IndexOf('@');
            int b = email.IndexOf("qq.com");

            Console.WriteLine($"@ index is {a}; qq.com index is {b}"); // @ index is 13; qq.com index is 14
            Console.WriteLine("+++++++++++++++++++++++++++++++++++++++");

            int c = email.Length;
            Console.WriteLine($"email len = {c}");  // email len = 20

        }

Substring 字符串截取,从一个长字符串截取一部分字符串

  • 语法

    string result = xxx.SubString(startIndex, endIndex) 
    
  • 代码

    static void Test5() {
                string email = "12421341@163.com";
                string number = email.Substring(0, 8);
                Console.WriteLine(number); // 12421341
            }
    
     static void Test5() {
                string email = "12421341@163.com";
                string number = email.Substring(0, 8);
                Console.WriteLine(number);
                Console.WriteLine("+++++++++++++++++");
    
                string number1 = email.Substring(0, email.IndexOf('@')); // 12421341
                Console.WriteLine(number1);
    
                string type = email.Substring(email.IndexOf('@') + 1);  // 163.com
                Console.WriteLine(type);
            }
    

    使用IndexOf配合Substring可以很好的解决获取字符串。

字符串比较

注意:区分大小写

  • 常用比较方法 ==

     static void Test6()
            {
    
                string name1 = "kobe";
                string name2 = "Kobe";
                string name3 = "kobe";
    
                Console.WriteLine("name1==name2  "+ (name1==name2)); // 注意这里必须加括号,因为优先级问题
                Console.WriteLine("name1==name3  " + (name1 == name3));
    
    
            }
    

    输出结果:

    name1name2 False
    name1
    name3 True

  • 使用方法比较 xxx.Equals

    static void Test6()
            {
    
                string name1 = "kobe";
                string name2 = "Kobe";
                string name3 = "kobe";
    
                Console.WriteLine("name1==name2  " + (name1 == name2));
                Console.WriteLine("name1==name3  " + (name1 == name3));
    
    
                Console.WriteLine("name1==name2  " + name1.Equals(name2)); // 这里不用加括号,因为对象.方法的优先级高
                Console.WriteLine("name1==name3  " + name1.Equals(name3));
            }
    
    

    这里的输出结果和刚刚的输出结果一样的。

ToLower 转换成小写

对于有些忽略大小写的场景比较,可以把两个比较的字符串统一变成大写,或者统一变成小写。

static void Test7() {

            string s1 = "fsdaFFkkfdsagvgKK";
            string s1ToLower = s1.ToLower();
            string s1ToUpper = s1.ToUpper();
            Console.WriteLine(s1ToUpper);
            Console.WriteLine(s1ToLower);
        }

ToUpper转换成大写

同上

Equals 比较方法

注意:== 或者 Equals方法默认只能比较”值类型“和”字符串类型“,对象类型是不能进行比较的

Trim 前后去空格

TrimStart 获取字符串前面的空格

TrimEnd 获取字符串后面的空格

LastIndexOf 匹配最后一个字符的位置

static void Test8()
        {
            string email = "123@qq.com@163.com";
            int index = email.LastIndexOf('@');
            Console.WriteLine(index);  // 10
        }

7.2 字符串格式化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lipiv1ee-1684053040578)(.\CSharp.assets\image-20230306122618454.png)]

7.3 空值&空对象

7.3.1 空值

定义空值的两种方式:

  • 方式1

    string empty = ""
    
  • 方式2

    string empty = string.Empty 
    
  • 代码

     static void Test9() {
                string s1 = "";
                string s2 = string.Empty;
    
                Console.WriteLine("s1==s2" + (s1 == s2)); // True
    
            }
    

7.3.2 空对象

空对象被赋值为null

null 关键字是一个空值

null与”“是两回事、

不能使用null调用方法

未将对象引用到对象的示例

7.4 高效处理字符串

StringBuilder类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ducegr2-1684053040579)(.\CSharp.assets\image-20230306124319039.png)]

7.5 转义字符

反斜杠 \ “.\\Envs\\Python”

使用但反斜杠的场景都需要转义。

@ @“.\Envs\Python”

注意:写到配置文件中不需要转义

如何使用配置文件中的配置内容

  1. 添加引用 System.Configurations
  2. using 命名空间
  3. ConfiguationManager.ConnectionStrings[“connString”].ToString()

7.6 字符串分割与连接

分割 Split


组合 Join

 static void Test10() {
            string s1 = "AA BB CC DD";
            string[] arr = s1.Split();  // 默认使用空格分割
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }

            string s2 = string.Join("-", arr);
            Console.WriteLine(s2);
        }

结果:

AA
BB
CC
DD
AA-BB-CC-DD

8 数组

8.1 基本概念

定义

数组也是一个变量,是一个具有连续空间,并且能够存储相同类型数据的空间

基本要素

  1. 标识符: 数组名
  2. 元素:数组内的元素名
  3. 类型:数组内元素的类型,必须是相同类型
  4. 下标:数组中元素的索引

操作数据的步骤

  1. 声明数组

    // 语法
    类型[] 数组名
        
    int[] scores // 声明一个数组,注意,只是声明。
    
  2. 数组实例化

    数组需要实例化,数组也是一个类型,所以需要分配空间。必须写清楚数组长度,否则不知道分配多大的空间

    // 语法
    // 已声明
    scores = new int[5];
    
    // 未声明
    int[] scores = new int[5];
    
  3. 数组赋值

    // 通过下标赋值:
    
    int[0] = 10
    
    int[1] = 11
    
    int[2] = 12
    
    int[3] = 14
    
    int[4] = 16
        
    // 声明+实例化+元素赋值放在一行中,适用于数组元素已经确定的场景
    
    方式1:
    
    int[] scores = new int[3]{1,2,3}
    
    方式2:
    
    int[] scores = new int[]{1,2,3,4,5}
    
    方式3:
    
    int[] scores = {1,2,3,4,5}
    

8.2 数组遍历

for 循环

  • 语法

    for (int i =0; i < arr.Length; i++){
    
    ​	arr[i]  // 对元素操作
    
    }
    
  • 实际代码,对数组中的每个元素加1,随后在输出

    
    

foreach循环

不关注索引,使用foreach

  • 语法

    foreach(int item in arr){
    
    ​	item // 就是每一个元素类型
    
    }
    
  • 实际代码

    
    

8.3 值类型与引用类型

值类型

基本数据类型再传递变量值时,传递的是变量的副本,开辟新的空间。

引用类型

引用类型变量传递自己,也就是变量本身,没有开辟新空间

常用的引用数据类型:

  • string字符串(.Net做了特殊包装,属于引用类型,但是需要当值类型来使用)
  • 数组
  • 系统库中的各种类
  • 自定义的类

8.4 底层类和关键字

String是.Net Framework的类

string是C#的关键字,也是String类的别名,表示一种数据类型,映射为.Net中的String

使用string时编译器会把它编译成String

string关键字,不能作为类、结构、枚举、字段、变量、方法、属性的名字,而String不是关键字,可以使用

9 常量枚举

9.1 常量

一个值不希望被别人修改的时候,可以使用常量

常量名建议全部大写

常量必须在定义的同时初始化,不能先定义后初始化

  • 语法

    const double PI = 3.14
    
  • 实际测试:

    static void Test11() {
                const double PI = 3.14;
                PI = 3.141;  // 报错
                Console.WriteLine(PI);
            }
    

9.2 枚举

基本概念

作用:可以用来定义”有限个变量值“,方便选择使用,避免出错

特点:可以变送一组描述性的名称,还可以有对应的整数值

语法

// 定义
public enum Gender{

​	Mela = 1, //整数值可以有也可以没有。

​	Female = 0

}

// 使用
Gender gender = Gender.Male 

gender = "m"  // 非法赋值,必须是枚举中的一个元素。

实际应用

 internal class Program
    {
        static void Main(string[] args)
        {
            Test1();
            Console.Read();
        }

        static void Test1() {
            Gender gender = Gender.Femela;
            Console.WriteLine(gender);
            Console.WriteLine((int)gender);
            gender = Gender.Mela;
            Console.WriteLine(gender);

        }
    }

    public enum Gender{
        Mela = 1,
        Femela = 0
    }

    public enum Week { 
        Sun,
        Sat,
        Fri,
        Sur,
        Wed,
        Thu,
        Mon
    }
}

输出结果:

Femela
0
Mela

面向对象Basic

10 类与对象

10.1 概念

编程大概有两种方式,分别是面向过程和面向对象。

面向过程可以实现所有的需求,但是面向过程的程序缺少模块化。

面向对象有两个比较重要的概念,分别是对象,类也是一种数据类型,描述了一类对象中的所有属性和方法。类主要是抽象能力。

  • 对象:

    • 静态属性:描述对象的基本特征信息
    • 动态行为:描述这个对象能做什么,如何做
  • 类中的内容

    • 类中可以有字段、属性、方法
    • 不建议类中只有字段
    • 类中可以只有属性、也可以只有方法

定义类的语法

  • 定义类 类名使用Pascal命名法:首字母必须大写.属性和方法一般首字母大写,字段首字母小写

    访问修饰符 关键字 类名
    public class Person
    {
    
    ​	// 字段
    
    ​	// 属性
    
    ​	
    
    ​	// 方法
    
    }
    
  • 创建对象

    // 类型 变量名 = 关键字 类名()  构造函数
    Person person = new Person();
    
  • 获取属性

    person.Attr 
    
  • 设置属性

    person.Attr = "xxxx"
    
  • 调用方法

    person.Method()
    

类的访问修饰符

  • 程序集的概念

    一个类库,或者一个exe可执行文件,叫做一个程序集

  • public

    public允许类被程序集外部访问

  • internal

    internal不允许被程序集外部访问

10.2 属性Property

小tips

  • 在VS中,prop 连续两个tab键,会生成属性

10.2.1 创建一个类,并实例化

成员变量(字段)和局部变量

  • 成员变量(字段)

    成员变量设置在类内方法外,成员变量一般用于类内部使用,一般设置访问优先级为private,不建议设置public的成员变量

  • 局部变量

    局部变量在类中的方法内

  • 代码

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class Person
        {
            
    
            private string _desc = string.Empty; // 成员变量(字段)
    
            public int PId { get; set; }
            public string Job { get; set; }
            public string Name { get; set; }
    
            public void ShowInfo() {
                int name = "Any";  // 局部变量
                Console.WriteLine($"PId={PId},Job={Job},Name={name}");
            }
        }
    }
    
    

创建类,并实例化

  1. 创建类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S7HTAXRH-1684053160759)(.\CSharp面向对象.assets\image-20230307104756186.png)]

    在项目中创建一个People类。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class People
        {
            public int PId { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
    
            public void ShowInfo()
            {
                Console.WriteLine($"PId={PId},Name={Name},Desc={Description}");
            }
        }
    }
    
    
  2. 实例化类

    在Program类中访问Person类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
    namespace OopProperty
    {
        public class Program
        {
            static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    
            static void Test1() {
                
                People  people= new People();
                people.PId = 1;
                people.Name = "Test";
                people.Description  = "Description";
                people.ShowInfo();
            }
        }
    }
    
    

    输出为:PId=1,Name=Test,Desc=Description

使用ildasm 查看C#的字节码

ildasm是一个反编译工具,通过这个工具可以把C#的源代码反编译成C#的中间语言(其实就是C#)的字节码。使用C#虚拟机运行的字节码。

所在位置:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools

ildasm软件的空白图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ptnp3UMo-1684053160760)(.\CSharp面向对象.assets\image-20230307111158600.png)]

查看源代码的文件夹:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKfbwqEe-1684053160761)(.\CSharp面向对象.assets\image-20230307111324414.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ppftHxUA-1684053160761)(.\CSharp面向对象.assets\image-20230307111352649.png)]

将debug文件夹中的可执行文件exe拖入到IL DASM中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BxCTR6Sr-1684053160761)(.\CSharp面向对象.assets\image-20230307111720786.png)]

点击任何一个组件可以看到里面的字节码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SLysr3ZE-1684053160762)(.\CSharp面向对象.assets\image-20230307111827822.png)]

自动为每个属性定义了一个成员变量,也可以手动设置成员变量用于保存属性的值

属性只是一个接口,真正保存数据的是字段

  • 手动定义字段来保存属性的值

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class People
        {
            private int _pid = 0; // 定义成员变量并赋初始值为0
            public int PId {  
                get { 
                    return _pid;  // 属性的get方法返回成员变量的值
                 }
                set { 
                    _pid= value;  // 属性的set方法设置成员变量的值
                }
            }
            public string Name { get; set; }
            public string Description { get; set; }
    
            public void ShowInfo()
            {
                Console.WriteLine($"PId={PId},Name={Name},Desc={Description}");
            }
        }
    }
    
    
  • 查看手动设置成员变量反编译的结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRw7TAM2-1684053160762)(.\CSharp面向对象.assets\image-20230307141606739.png)]

    注意:编译器没有为PId属性创建新的k__BackingField字段,而是使用了手动的 _pid成员变量。

  • 自动属性与手动设置成员变量与属性关联具有相同的效果

    public int PId {get; set;}
    //等价于
    private int _pid = 0;
    public int PId {
        get {
            return _pid;
        }
        set {
            _pid = value   // 这里的value是编译器确定的
        }
    }
    
    // 当调用obj.PId 时 返回 _pid 成员变量的值
    // 当调用obj.PId = xxx  给属性赋值时,将xxx赋值给_pid 
    

10.2.2 自动属性

在C# 3.0版本开始使用自动属性,在之前的版本都是通过手动与成员变量做关联。

在使用断点调试时,当给属性赋值时,调用了set方法,给对应的成员变量赋值,当读取属性值时,调用了get方法,从对应的成员变量获取值。

属性本身并没有保存任何数据,数据是保存到属性对应的私有成员变量中。

属性的作用是访问对象数据的入口。

10.2.3 属性与成员变量(字段)的比较

成员变量(字段)

  1. 字段是为类的内部方法、或者方法之间传递数据使用,强调对内使用

  2. 访问修饰符:一般成员变量都是用private修饰,基本不用public

  3. 读写:字段读写均可,也可以用readonly限制为只读,但是不能添加业务逻辑,比如一个人的身高,用户输入超过3米,显然是不合理的,这种使用字段就很难限制。

  4. 代码

    可以直接获取成员变量和设置成员变量的值

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class People
        {
           
            //public readonly int _age;
            public int _age;
            
        }
    }
    
    
    static void Test2() { 
                People  people = new People();
                people._age = 11;  // 直接设置成员变量的值
                Console.WriteLine(people._age);  // 获取成员变量的值
            }
    

    可以或者只读属性的值,但是不能设置只读属性的值

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class People
        {
           
            public readonly int _age;
            //public int _age;
            
        }
    }
    
    static void Test2() { 
                People  people = new People();
                people._age = 11;  // 无法分配到只读字段 直接编译无法通过
                Console.WriteLine(people._age);  
            }
    

属性

  1. 属性是对外提供数据访问、本身不保存数据,强调对外使用,对外部的接口

  2. 修饰符:属性都是用public修饰、从来不用private

  3. 读写:属性可以轻松的实现单独读写控制,并且可以添加任意需要的逻辑

  4. 对于一些场景,必须使用属性,比如dgv列表必须强制使用属性

  5. 代码

    为属性添加业务逻辑,比如人类,年龄这个属性,不允许超过100岁,超过100直接设置成100

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Schema;
    
    namespace OopProperty
    {
        internal class People
        {
            
            private int _age;
    
            public int Age
            {
                get
                {
                    return _age;
                }
                set
                {
                    if (value > 100)
                    {
                        _age = 100;
                    }
                    else
                    {
                        _age = value;
                    }
                }
            }
        }
    }
    
    
    static void Test3()
            {
                People people = new People();
                people.Age = 101;
                Console.WriteLine(people.Age);
                People people1 = new People();
                people1.Age = 11;
                Console.WriteLine(people1.Age);
            }
    

    同样可以设置只读属性,把set删除即可

10.2.4 属性的新特性 (>4.6)

属性直接初始化

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Schema;

namespace OopProperty
{
    internal class People
    {
        
        public string Name { get; set; } = "张三";
        
    }
}

 static void Test4() {
            People people = new People();
            Console.WriteLine(people.Name);  // 张三

            people.Name = "里斯";
            Console.WriteLine(people.Name);  //             里斯
        }

属性表达式,设置只读属性的三种不同方式

  • 直接使用箭头函数

    public class People{
        
        public int Age {get=>100;}
        
    }
    
  • 变种方式2

    public class People{
        
        public int Age =>100;}
        
    }
    
  • 变种方式3

    public class People{
        
        public int Age {get;} = "111"
        
    }
    

10.2.5 类属性也是一种类型的场景

一个类的属性可以是另一个类

比如一个汽车类,有一台发动机,发动机也是一种类。

  • 定义发动机类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class Turbo
        {
            public float Volumn { get; set; }
    
            public string Type { get; set; }
        }
    }
    
    
  • 定义汽车类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopProperty
    {
        internal class Car
        {
            public string Brand  { get; set; }
    
            public Turbo CarTurbo { get; set; }
    
    
            public void ShowInfo() {
    
                Console.WriteLine($"汽车品牌:{Brand},发动机型号:{CarTurbo.Type},发动机排量:{CarTurbo.Volumn}L" );
            }
        }
    }
    
    
  • 调用方代码

    static void Test5() { 
                
                Turbo ea888 = new Turbo();
                ea888.Type = "EA888";
                ea888.Volumn = 2.0f;
                Car audiA6 = new Car();
                audiA6.Brand = "audi";
                audiA6.CarTurbo = ea888;
                audiA6.ShowInfo();  
            }
    

    输出:汽车品牌:audi,发动机型号:EA888,发动机排量:2L

10.3 类的访问修饰符

测试访问修饰符,类的默认访问修饰符为internal

  1. 在解决方案中创建一个新的类库

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Es8a3DD-1684053160762)(.\CSharp面向对象.assets\image-20230307233859224.png)]

    在解决方案中有一个控制台的项目有一个类库的项目,类库的项目和控制台项目分别属于两个不同的程序集

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WwFAW8Or-1684053160763)(.\CSharp面向对象.assets\image-20230307234100748.png)]

    在类库项目中随便创建一个类,定义为internal类型

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ClassLibrary1
    {
        internal class Class1
        {
            public int MyProperty { get; set; }
        }
    }
    
    
  2. 在控制台项目中引入类库,引用命名空间,使用类

    static void Test6() {
                Class1 c1 = new Class1(); // Class1不可访问,一位内它具有一定的保护级别
            }
        
    
  3. 在控制台项目中使用类库中的public类

    namespace ClassLibrary1
    {
        public class Class1
        {
            public int MyProperty { get; set; }
        }
    }
    

    修改访问修饰符之后,就不再报错了。

10.4 方法

10.4.1 构造方法

定义

构造方法是在对象创建的时候,调用的方法,通过构造方法对实例进行初始化。

Object obj = new Object() 
// 大家都知道,通过new关键字,创建一个类的实例,也叫类的实例化
// new关键字其实就是在堆中开辟了一块内存
// Object()就是调用类的构造方法,在构造方法中,可以对实例进行初始化

类实例化的过程可以分割成两步,第一步是开辟内存,第二步是初始化

一个类中,在我们没有添加任何构造方法的时候,编译器会生成一个无参数的构造方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OopMethod
{
    internal class Person
    {
        
    }
}

通过IL DSAM反编译工具,查看反编译后的结果,一个空白类中会默认有一个无参数无返回值的构造方法,当然了,所有的构造方法都不能有返回值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mafYOA9M-1684053160763)(.\CSharp面向对象.assets\image-20230308161921710.png)]

  • 构造方法小结:
    1. 构造方法必须和类名一致
    2. 构造方法不能有任何返回值
    3. 构造方法可以有参数,也可以没有参数
    4. 构造方法支持重载,重载的条件是参数类型或者参数个数不一样

创建构造方法

  • 语法

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Person
        {
            public Person()
            {
                Console.WriteLine("无参数构造方法");
            }
        }
    }
    
    

    在另外的一个类中,实例化这个类:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    
            static void Test1() { 
                Person person   = new Person();
            }
        }
    }
    

    打印:无参数构造方法

    这就说明,在实例化的过程中,执行了构造方法。

  • 构造方法可以重载

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Person
        {
            public Person() {
                Console.WriteLine("无参数构造方法");   
            }
    
            public string Name { get; set; } = string.Empty;
            public int Age { get; set; } = 0;
    
            public Person(string name) {
                Console.WriteLine("有一个name参数的构造方法");
                Name = name;
            }
    
            public Person(string name,int age) : this(name)
            {
                Console.WriteLine("有两个参数的构造方法");
                Age = age;
            }
        }
    }
    
  • 构造方法可以进行嵌套

    public Person(string name,int age) : this(name)
            {
                Console.WriteLine("有两个参数的构造方法");
                Age = age;
            }
    
    // 构造方法:this() 会根据this中参数的不同,调用其他的不同的构造方法
    

10.4.2 类初始化的步骤

  1. 赋值的成员变量

  2. 显示声明的属性

  3. 构造方法

  4. 代码:

    注意:下面代码注释中标注的数字代码初始化的顺序

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Person
        {
    
            private int _id = 1000;  //  #1
            public Person()
            {
                Console.WriteLine("无参数构造方法");
            }
    
            public string Name { get; set; } = string.Empty;  //  #2
            public int Age { get; set; } = 0;   //  #3
    
            public Person(string name)
            {
                Console.WriteLine("有一个name参数的构造方法");
                Name = name;
            }
    
            public Person(string name, int age) : this(name)   //  #4
            {
                Console.WriteLine("有两个参数的构造方法");
                Age = age;
            }
        }
    }
    
    

    成员变量必须先初始化,否则属性就无法初始化,因为属性的数据存储在成员变量中,不管是自动属性还是手动设置的属性。

10.4.3 this关键字在类中的两个作用

一个构造方法调用其他的构造方法

如果一个类中,有三个构造方法:
分别是:
无参构造方法
有一个参数的构造方法,对应Name属性
有两个参数的构造方法,对应Name属性和Age属性

public class Person{
    public string Name{get;set;}
    public int Age {get; set;}
    
    public Person(){
        
    }
    
    public Person(string name){
        Name = name;
    }
    
    public Person(string name, int age){
        Name = name; // 这行其实就是一个参数的构造函数
        Age = age;
    }
    // 这种情况下会出现很多重复的代码
    // 对于这种情况可以使用以下情况进行简化:
    public Person(string name, int age):this(name){ 
        Age = age; 
    }
    
    
} 	

在类中通过this访问属性、字段、方法

this 指代的其实就是当前类的一个实例对象,通过this关键字,可以获取成员变量、属性和方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace OopMethod
{
    internal class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }

        private string _hobby;

        public string Hobby
        {
            get
            {
                return this._hobby;
            }
            set
            {
                this._hobby = value;
            }
        }


        public Student() { }

        public Student(int id, string name, string hobby) { 
            
            this.Id= id; // this.Id 调用的是属性
            this.Name = name;
            this.Hobby = hobby;
        }


        public void Method1()
        {
            this.Method2();
        }

        public void Method2()
        {
            Console.WriteLine($"{this.Id},{this.Name},{this.Hobby}");

        }
    }
}

10.4.4 对象初始化器

对象初始化器是显示的给对象进行初始化,对象初始化器与对象内部的构造方法需要配合使用,而不是相互替代的关系。

  • 语法

    Person person = new Person(){
    	Id = 1000,  // 注意。这里是逗号
    	Name = "稀罕"
    };  // 注意,这里的;一定要加
    
  • 实际使用

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Car
        {
            public Car() { }
    
            public Car(string type)
            {
                Type = type;
            }
    
            public string Type { get; set; }
    
            public string Year { get; set; }
    
            public void ShowInfo()
            {
                Console.WriteLine($"{this.Type}:{this.Year}");
            }
        }
    }
    
    

    调用方代码:

    static void Test3() {
                Car car = new Car("audi") {  
                    Type ="bmw", // 对象初始化器覆盖了构造函数初始化的属性值
                    Year = "2020"
                };
    
                car.ShowInfo(); // bmw:2020
            }
    

10.4.5 实例方法

语法

方法名使用Pascal命名法,必须首字母大写

访问修饰符 返回值类型 方法名(参数列表){
	// 方法体

}

代码

  • 无参数,没有返回值

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Car
        {
            public Car() { }
    
            public Car(string type)
            {
                Type = type;
            }
    
            public string Type { get; set; }
    
            public string Year { get; set; }
    
            public void ShowInfo()
            {
                Console.WriteLine($"{this.Type}:{this.Year}");
            }
        }
    }
    
    
  • 无参数,有返回值

    public string OutInfo() {
                string info = $"{Type}:{Year}";
                return info;
            }
    

    使用调用方进行调用:

     static void Test4()
            {
                Car car = new Car("audi");
                car.Year = "1111";
                string info = car.OutInfo();
                Console.WriteLine(info);
            }
    
  • 有参数,没有返回值

     public void HasParamNoRet(int start)
            {
                start += 100;
                Console.WriteLine($"{start}");
            }
    

    调用

    
    
  • 有参数,有返回值

     public int HasParamHasRet(int start)
            {
                return start += 100;
            }
    

    调用

    static void Test5() {
    
                Car car = new Car("奔驰");
                car.Year = "1141";
                car.HasParamNoRet(11);
                int result = car.HasParamHasRet(11);
                Console.WriteLine("result="+result);
            }
    
  • 返回数组

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace OopMethod
    {
        internal class Tyre
        {
            public Tyre() { }
            public Tyre(int addr) {
                Addr = addr;
            }
            public int Addr { get; set; }
        }
    }
    
    
     public Tyre[] TyreArray() {
                Tyre t1 = new Tyre(1);
                Tyre t2 = new Tyre(2);
                Tyre t3 = new Tyre(3);
                Tyre t4 = new Tyre(4);
    
                Tyre[] Tyres = new Tyre[4];
                Tyres[0] = t1;
                Tyres[1] = t2;
                Tyres[2] = t3;
                Tyres[3] = t4;
                return Tyres;
            }
    
           
        }
    
  • 返回对象

     public Engine GetEngine() { 
                Engine engine= new Engine("ea888");
                return engine;
            
            }
    
  • 调用方代码

    static void Test6() {
                Car car = new Car("沃尔沃");
                Tyre[] results =  car.TyreArray();
                foreach (var item in results)
                {
                    Console.WriteLine($"轮胎位置:{item.Addr}");
                }
                Console.WriteLine("____________________________________");
    
                Engine e = car.GetEngine();
                Console.WriteLine(e.Name);
    
            
            }
    

    输出:

    轮胎位置:1
    轮胎位置:2
    轮胎位置:3
    轮胎位置:4


    ea888

10.4.6 静态方法

定义

实例方法是通过对象.实例方法进行调用的

静态方法是通过类名.静态方法进行调用的

静态方法的特点是不需要构建对象就可以直接使用。

静态方法跟随类一起加载,被加载在栈空间,并且不会被垃圾回收,直到程序退出执行。

静态方法中不能访问实例字段、方法、属性。只能访问静态字段、属性。

10.4.7 不确定参数

当一个方法的参数不固定时,用params关键字实现可变参数

语法

修饰符 返回类型 方法名(params 类型[] 变量名){

// 方法体
}

使用不确定参数的注意点:

  • 该参数必须放到方法参数最后,并且只能用一次
  • 参数数组必须是一维数组
  • params不能配合ref和out组合使用

测试代码

public void ShowInfo2(int a, string[] info) {
            foreach (var item in info)
            {
                Console.WriteLine(item);
            }

        }

调用方:

static void Test7() {

            Car car = new Car("夏利");
            car.Year = "1990";

            car.ShowInfo2(1,new string[] { "11", "12", "13" });

        }

使用params关键字:

 public void ShowInfo3(int a,  params string[] info)
        {
            foreach (var item in info)
            {
                Console.WriteLine(item);
            }

        }

调用方:

static void Test7() {

            Car car = new Car("夏利");
            car.Year = "1990";

            car.ShowInfo3(1,"11", "12", "13" );

        }

10.4.8 垃圾回收机制

.Net虚拟机自带垃圾回收机制,不需要程序员操作。

11 泛型集合

泛型(generic)是编程语言的一种风格,要求程序员明确特定的类型在进行初始化工作。

11.1 集合类型

数组和集合类型比较相似,集合比数组更加灵活,可以动态的扩展。

语法

//List<type> 

List <string> nameArr = new List<string>();  // 创建一个实例

常用操作

static void Test8()
        {

            // 实例化一个集合对象
            List<string> nameList = new List<string>();

            // 增加元素
            nameList.Add("张三");
            nameList.Add("李四");

            // 获取元素的个数
            Console.WriteLine("当前List的长度是:" + nameList.Count);

            // 集合的初始化器
            List<string> player = new List<string>() { "kebi", "james", "curry" };

            // 集合转换成数据
            string[] res = player.ToArray();


            // 将数组放到这个结构中
            player.AddRange(new string[] { "haga发生", "fasfa范德萨" });

            // 集合添加数组 
            string[] names = { "柯布", "柯南" };


            // 删除其中一个
            player.Remove("haga发生");

            player.AddRange(names);

            // 按照索引删除
            player.RemoveAt(1);
			
   			// 在指定位置插入元素
            player.Insert(1, "张三");  // 在索引1的位置,也就是第二个元素,插入张三
			
    		// 判断一个元素是否在这个集合内
    		bool isExists = player.Contains("张三");
    		
            // 清除所有:
            player.Clear();
        }

集合初始化器

static void Test9() { 
            
            List<string> names = new List<string>() { 
                "张三", 
                "李四",
                "王五",
                "马六"
            };

            foreach (var item in names) {

                Console.WriteLine($"{item}");
            }
}

输出:

“张三”,
“李四”,
“王五”,
“马六”

集合复制

static void Test9()
        {

            List<string> names = new List<string>() {
                "张三",
                "李四",
                "王五",
                "马六"
            };

            List<string> newNames = new List<string>(names);

            foreach (var item in names)
            {

                Console.WriteLine($"{item}");
            }

            foreach (var item in newNames)
            {
                Console.WriteLine($"new-{item}");
            }
          
        }

输出:

张三
李四
王五
马六
new-张三
new-李四
new-王五
new-马六

11.2 字典类型

语法

Dictinary<k,v> infos = new Dictinary<k,v>();

一般k都是字符串类型的

v可以是对象,也可以是别的类型

Dictinary的结构:

k1:v1

k2:v2

k3:v3

字典初始化器

static void Test10() { 
        
            Dictionary<string , string> infos = new Dictionary<string, string>() {
                ["张三"] = "喜欢读书",
                ["李四"] = "喜欢唱歌"
            };

            foreach (var item in infos.Keys) {
                Console.WriteLine($"keys:{item}");
            }

            foreach (var item in infos.Values) {
                Console.WriteLine($"values:{item}");
            }

        }

输出:

keys:张三
keys:李四
values:喜欢读书
values:喜欢唱歌

字典的常用方法与集合的常用方法是相同的

CSharp高级进阶

1 OOP - 继承 inherit

概念

类与类之间的继承,主要是继承属性和方法,比如生活中的祖辈与父辈之间的继承。

继承必须具有单根性,一个类只能有一个父类,不能同时继承多个类,也不能循环继承和反向继承

代码测试1

  • Person.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._01_继承
    {
        internal class Person
        {
            public Person() { }
            public string name { get; set; }
            
            public int age { get; set; }
    
            public string gender { get; set; }
    
            public void doEat() {
                Console.WriteLine("吃饭ing...");
            }
        }
    }
    
    
  • Student.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._01_继承
    {
        internal class Student:Person
        {
            public Student() { }
            public string School { get; set; }
    
            public void study() {
                Console.WriteLine($"{this.name} is study in {this.School}");
            }
        }
    }
    
    
  • Program.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using ZJF.advance._01_继承;
    
    namespace ZJF.advance
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Test1(); 
                Console.ReadLine();
            }
    
            static void Test1() { 
                Student s1 = new Student();
                s1.name = "张三";
                s1.age = 1;
                s1.gender = "男";
                s1.School = "希望学校";
                s1.study();
            }
        }
    }
    
    
  • 这套代码的执行流程如下:

    1. 先执行Student的无参构造,然后执行Person的无参构造
    

测试代码2

有参构造方法:

  • Person.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._01_继承
    {
        internal class Person
        {
            public Person() { }
    
            public Person(string name) { 
                this.name = name;
            }
    
            public Person(string name, int age):this(name) { 
                this.age = age;
            }
            public Person(string name, int age, string gender):this(name,age)
            {
                this.gender = gender;
            }
    
            public string name { get; set; }
            
            public int age { get; set; }
    
            public string gender { get; set; }
    
            public void doEat() {
                Console.WriteLine("吃饭ing...");
            }
        }
    }
    
    
  • Student.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._01_继承
    {
        internal class Student:Person
        {
            public Student() { }
    
            public Student(string name, int age, string gender, string school) : base(name, age, gender) { 
                this.School = school;
            }
    
            public Student(string school)
            {
               this.School = school;
            }
    
            public string School { get; set; }
    
            public void study() {
                Console.WriteLine($"{this.name} is study in {this.School}");
            }
        }
    }
    
    
  • Program.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using ZJF.advance._01_继承;
    
    namespace ZJF.advance
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Test1();
                Console.ReadLine();
            }
    
            static void Test1() { 
                Student s1 = new Student("李四",2,"nv","清华大学");
                s1.study();
            }
        }
    }
    
    
  • 代码执行顺序:

    1. Student s1 = new Student("李四",2,"nv","清华大学");
    2. 
    3. 
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMczgdu1-1684052960420)(D:\markdown\C#系列\CSharp高级进阶.assets\image-20230429210033497.png)]

【1】子类构造方法没明确调角父类的哪个构造方法时,默认调用父类的无参数构造方法。(前提:父类必须要有无参数的构造方法)

【2】父类没有无参数的构造方法,子类构造方法,必须显式的说明调用父类的哪个有参数的构造方法。

【3】注意:调用父类的构造方法,请使用base关键字。

1.1 关键字base this

this关键字

this关键字表示当前类的实例,在构造方法中用于指向当前类的实例

  • 用法1:在构造方法中

    
    
  • 用法2: 在构造方法传递中

    
    
  • 用法三:在类内部调用内部的属性或者方法

    
    

base关键字

base关键字表示调用当前类的父类中的属性或者方法

  • 用法1:调用父类中的属性
  • 用法2:调用父类中的构造方法

1.2 protected关键字

protected关键字修饰的属性或者方法,只能在父类或者子类类内使用,不能在对象中使用

1.3 private 关键字

private关键字是私有对象,只能在父类中使用,子类不可见

1.4 abstract关键字,抽象方法

抽象方法必须在抽象类中

  • Person.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._03_kw_abstract
    {	
        // 抽象方法必须放在抽象类中,否则会报错
        public abstract class Person
        {
            public string name  { get; set; }
    
            public string gender { get; set; }
    
            public Person() { }
    
            public Person(string name, string gender)
            {
                this.name = name;
                this.gender = gender;
            }   
    		// 声明抽象方法,抽象方法在父类中不需要写明函数体
            // 抽象方法的目的是规范子类的方法名,形参、和返回值,如果不是抽象方法,子类可以重写也也可以不重写
            public abstract void Play();
        }
    }
    
    

子类实现

  • 子类必须重写抽象类的抽象方法

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._03_kw_abstract
    {
        public class Student:Person
        {
            public override void Play() {
                Console.WriteLine("学生放学后开始玩耍!");
            }
        }
    }
    
  • 如果非抽象子类不重写抽象方法会报错:

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._03_kw_abstract
    {
        public class Student:Person
        {
            //public override void Play() {
            //    Console.WriteLine("学生放学后开始玩耍!");
            //}
        }
    }
    
    
    //错误	CS0534	“Student”不实现继承的抽象成员“Person.Play()”	ZJF.advance	// //D:\code\csharp_home\ZJF.advance\ZJF.advance\03_kw_abstract\Student.cs	9	活动
    
  • 如果子类是抽象类可以不重写抽象方法

    namespace ZJF.advance._03_kw_abstract
    {
        public abstract class Student:Person
        {
            
        }
    }
    // 如果子类也是抽象类,那么可以不实现抽象方法
    
  • 注意:

    抽象方法不能直接new

    抽象方法必须在抽象类中

    抽象类中可以没有抽象方法

2 OOP-多态

同一个抽象方法,不同子类实现,由父类实例调用

2.1 继承多态

引言

多态是面向对象的很重要特性,通过多态可以很好的实现面向对象编程中的开-闭原则。

开:增加类:通过增加不同的功能类实现需求的变化

闭:修改类:尽量不因为需求的变化修改类的内部

  • 多态的基本实现原理

    // 父类类型对象调用抽象方法,可以访问到不同子类的实现
    public abstract class Car{
        public abstract void run();
    }
    
    // 定义两个不同品牌的汽车继承自Car类
    public class Audi:Car{
        public override void run(){
            Console.WriteLine("奥迪因为Quattro跑的很快!");
        }
    }
    
    public class BMW:Car{
        public override void run(){
            Console.WriteLine("宝马的操控性很好!")
        }
    }
    
    public class Program{
        public static void main(){
            Car car = new Audi();
            car.run(); // 具体执行哪个run是根据等号右边的实例对象决定的
            // 输出:奥迪因为Quattro跑的很快
            car = new BMW();
            car.run();
            // 输出:宝马的操控性很好
        }
    }
    

继承多态的两种实现方式

多态的目的更多的是为了系统的扩展性。

比如一个关系数据库管理系统,可以对接不同的关系数据库,可以这样实现:

  1. 定义一个数据库抽象父类,并且定义抽象方法,实现两个子类

    public abstract class Database{
        
        public abstract void runSQL();
    }
    
    public class MysqlDB:Database{
        public override void runSQL(){
            Console.WriteLine("Mysql数据库执行SQL");
        }
    }
    
    public class PgsqlDB:Database{
        public override void runSQL(){
            Console.WriteLine("PgSQL数据库执行SQL")
        }
    }
    
  2. 使用类中的方法

    public class Program{
        public static void main(){
            
        }
        
        public static Test1(){
            Database db = getDB()
        	db.runSQL()
        }
        
        public static Database getDB(){
            return MysqlDB()
        }
    }
    
  3. 如果需要扩展这个类

    // 添加一个新的扩展类
    public class OracleSQL:Database{
        public override void runSQL(){
            Console.WriteLine("Oracle数据库执行SQL")
        }
    }
    
  4. 使用类中的方法

    // 不需要修改Test1这个方法
    
    // 只需要修改getDB()方法即可
     public static Database getDB(){
        return OracleDB()
     }
    
  • 子类对象做为方法的返回值

    上面数据库中的案例所示
    
  • 父类对象做为方法的形式参数

    public static Test1(Database db){
        	db.runSQL()
        }
    
    // 使用Test1
    Test1(MysqlDB())
    

3 虚方法

父类内部由默认实现的方法,对子类的实现没有强制性,子类可以重写也可以不重写,虚方法也可以实现多态

定义

  • 语法

    public virtual void FuncName(){
        // 虚方法默认的方法体
    }
    

虚方法和抽象方法的使用选择

如果父类需要由默认的实现,使用虚方法,如果父类不需要默认实现,使用抽象方法

子类可以实现虚方法,也可以不实现虚方法,使用父类默认的虚方法实现

  • Father.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._05_虚方法
    {
        internal class Father
        {
            public virtual void money() {
                Console.WriteLine("爸爸有钱都是儿子的");
            }
        }
    }
    
    
  • Son.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._05_虚方法
    {
        internal class Son:Father
        {
            public override void money()  // 可以重写也可以不重写
            {
                base.money(); // 默认调用父类实现的默认方法,也可以加一些自己的实现
                Console.WriteLine("儿子自己也赚了一笔钱!");
            }
        }
    }
    
    
    

4 new关键字

在子类方法中,使用new关键字,可以实现对父类“同名方法”的覆写。

5 接口

5.1 面向接口编程

接口是规范了一系列方法的集合

5.2 创建接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ZJF.advance._07_接口
{
    public interface IPerson
    {
         void Eat();

        void Sleep();

        string Speek(string info);
    }
}

接口的关键是interface,一般接口名称都是以大写字母I开头,

接口类中只能定义方法,不能实现方法,定义方法的格式如下:

返回值类型 方法名(参数类型 参数名)

接口中的方法不能写仿问修饰符

5.3 创建接口实现类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ZJF.advance._07_接口
{
    public class PersonImpl : IPerson
    {
        public string Name { get; set; }

        public PersonImpl() { }

        public PersonImpl(string name)
        {
            this.Name = name;
        }

        public void Eat()
        {
            Console.WriteLine($"{this.Name} is eating......");
        }

        public void Sleep()
        {
            Console.WriteLine($"{this.Name} is Sleeping......");
        }

        public string Speek(string info)
        {
            Console.WriteLine($"{this.Name} is Speeking.....{info}.");
            return "hhh";
        }
    }
}

接口的实现类通常命名为XxxxImpl 表示对IXxxx类的实现。

实现类必须实现接口类的所有方法,所以接口类必须遵循最小化原则,一个接口只负责特定的事情。

5.4 基于接口实现多态

前面讲了通过继承抽象类实现多态,也可以通过接口实现多态。

通过继承实现多态有两种方式:

方式1:抽象类对象做为返回值

static void Test1(){
    BaseObj baseobj = GetObj();
	baseobj.xxx();
}

static void GetObj(){
    return SonObj();   // SonObj必须是BaseObj的子类
}

方式2:抽象类对象做为函数参数

static void Test2(BaseObj obj){
    obj.xxx();
}

// 调用者代码:
Test2(SunObj())
  • 通过接口实现多态

  • IScreen.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._08_接口2
    {
        
        /// <summary>
        /// 屏幕接口类
        /// </summary>
        public interface IScreen
        {
            /// <summary>
            /// 打开
            /// </summary>
            void Open();
    
            /// <summary>
            /// 关闭
            /// </summary>
            void Close();
    
            /// <summary>
            /// 表演节目
            /// </summary>
            /// <param name="info"></param>
            void Show(string info);
        }
    }
    
    
  • TVScreenImpl.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._08_接口2
    {
        public class TVScreenImpl : IScreen
        {
            public void Close()
            {
                Console.WriteLine("关闭电视屏幕"); ;
            }
    
            public void Open()
            {
                Console.WriteLine("打开电视屏幕");
            }
    
            public void Show(string info)
            {
                Console.WriteLine($"电视屏幕播放:{info}");
            }
        }
    }
    
    
  • PadScreenImpl.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.advance._08_接口2
    {
        public class PadScreenImpl : IScreen
        {
            public void Close()
            {
                Console.WriteLine("Pad关闭屏幕");
            }
    
            public void Open()
            {
                Console.WriteLine("Pad打开屏幕");
            }
    
            public void Show(string info)
            {
                Console.WriteLine($"Pad查看{info}");
            }
        }
    }
    
    
  • 使用层:

    		 static void Test6() {
                IScreen screen = GetScreen();
                screen.Open();
                screen.Close();
                screen.Show("体育频道");
            }
    
            static IScreen GetScreen()
            {
                //return new TVScreenImpl();  // 只需要返回不同的实现类即可
                return new PadScreenImpl();
            }
    
  • 使用层:接口对象做为方法

    		static void Test7(IScreen obj) {
                obj.Open();
                obj.Close();
                obj.Show("体育频道");
            }
    		// 调用方调用Test7
    		Test7(new TVScreenImpl());   // 传递对象到
            Test7(new PadScreenImpl());
    

6 反射

6.1 概念

通过使用一个“程序集”把我们要使用得类的“命名空间、类名”通过“字符串”方式,使用反射技术,从而得到这个类的对象。

创建类的对象,有两种方式:

  • 直接创建

    new XXX

  • 反射创建

    通过反射创建

  • 基本使用

    1. 引入命名空间:using System.Reflection
    2. 使用程序集:Assembly (程序集分为exe(可执行文件)、dll(类库))
    3. 反射结果为接口类型,实现解耦

6.2 实现反射

示例代码1

  • Car 接口

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.InterfaceReflection._1.接口反射
    {
        public interface ICar
        {
            string Name { get; set; }  // 定义属性
            string Description { get; set; }  // 定义属性
            void Run();  // 定义方法
        }
    }
    
  • Audi接口实现类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Remoting.Messaging;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.InterfaceReflection._1.接口反射
    {
        public class AudiImpl : ICar
        {
            private string _name;
            private string _description;
            public string Name { get => this._name; set => this._name=value; }
            public string Description { get => this._description; set => this._description=value; }
    
            public Audi() { }  // 无参构造方法
    
            public Audi(string name, string des) {  // 有参构造方法
                this._name = name;
                this._description = des;
            }   
    
            public void Run()
            {
                Console.WriteLine($"{this.Name} is Running!!!");  // 实现Run方法
            }
        }
    }
    
    
  • 使用反射创建对象

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    // 【1】 添加引用
    using System.Reflection;  
    using ZJF.InterfaceReflection._1.接口反射;
    
    
    namespace ZJF.InterfaceReflection
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                // 【4】 测试
                Test1();
                Console.ReadLine();
            }
    
    
            static void Test1() {
               	// 【2】 使用new创建模块
                Car audi = new Audi("奥迪", "good car");
                audi.Run();
                try {
                    // 【3】 使用反射创建模块
                    // 【3.1】Load("程序集").CreateInstance("全连接类名称")
                    var tag = (Car)Assembly.Load("ZJF.InterfaceReflection").CreateInstance("ZJF.InterfaceReflection._1.接口反射.Audi");
                    tag.Name = "奥迪111";
                    tag.Description = "good car !!!!!";
                    tag.Run();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                
            }
    
    
        }
    }
    
    

示例代码2

将程序集和完整类名通过配置文件提供给用户,提供很好的扩展性。

  • 定义一下配置文件App.config

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup> 
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
        </startup>
      <appSettings>
        <add key="assName" value="ZJF.InterfaceReflection"/>  // 程序集可以是可执行文件,也可以是dll文件
        <add key="className" value="ZJF.InterfaceReflection._1.接口反射.Audi"/>  
      </appSettings>
    </configuration>
    
  • 定义对象工厂类ObjectFactory.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using System.Configuration;
    using ZJF.InterfaceReflection._1.接口反射;
    
    
    namespace ZJF.InterfaceReflection._2.反射工厂类
    {
        public class ObjectFactory
        {
    		// 【1】 定义程序集名称
            private static string assName = ConfigurationManager.AppSettings["assName"];
    
            public static Car GetObject() {
                // 【2】 定义类名
                string className = ConfigurationManager.AppSettings["className"];
                // 【3】 返回程序集对象
                return (Car)Assembly.Load(assName).CreateInstance(className);
            } 
        }
    }
    
  • 调用

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Reflection;
    using ZJF.InterfaceReflection._1.接口反射;
    using ZJF.InterfaceReflection._2.反射工厂类;
    
    namespace ZJF.InterfaceReflection
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                //Test1();
                Test2();
                Console.ReadLine();
            }
    
            static void Test2() {
                var obj = ObjectFactory.GetObject();
                obj.Name = "奥迪汽车";
                obj.Run();
            }
        }
    }
    
    

示例代码三

通过反射来实现扩展

7 泛型

1、回顾前面:泛型集合List、Dictionary<k,v>
所在命名空间:System.Collections.Generic;

2、泛型最大的好处:定义的时候,不明确具体的类型,谁使用,谁明确类型。让某一个方法,或者某一个类更具有通用性。

3、泛型概念:它是一种程序特性,泛化(将具体的,扩展为一般的)方法参数的类型。

4、泛型使用:泛型集合、泛型方法、泛型类、泛型委托…

5、泛型延伸:为了后面学习高级委托、多线程做准备。

泛型最大的好处就是:在定义的时候不明确类型,在使用的时候明确类型,让程序具有很好的泛化性,提高程序的通用性

7.1 List和Dictionary回顾

List是一个容器,容器内可以存储任意类型的变量,可以是基本类型也可以是引用类型。

List的用法

  • 先准备一个实体学生类:Student.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.GenericTech._01_回顾List和Dictionary
    {
        public class Student
        {
            // 学生ID
            public int SId { get; set; }
            // 姓名
            public string Name { get; set; }
            // 年龄
            public int Age { get; set; }
    		
            public Student() { }
    
            public Student(int sId, string name, int age)
            {
                SId = sId;
                Name = name;
                Age = age;
            }
    		
            public void Info() {
                Console.WriteLine($"同学:{this.Name} 今年 {this.Age}岁了!");
            }
        }
    }
    
    
  • 创建一个测试类TestListDict.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.GenericTech._01_回顾List和Dictionary
    {
        public class TestListDict
        {
            public void Test1() { 
                List<string> nameList = new List<string>() { 
                    "张三",
                    "李四",
                    "王五"
                };
    
                Console.WriteLine($"nameList 一共有{nameList.Count}个元素");
    
                foreach (string name in nameList)
                {
                    Console.WriteLine($"{name}");
                }
    
            }
    
            public void Test2()
            {
                List<int> nums = new List<int>();
    
                for (int i = 0; i < 10; i++) {
                    nums.Add(i);
                }
    
    
                Console.WriteLine($"nums 中一共有{nums.Count}个元素");
    
                foreach (int i in nums)
                {
                    Console.WriteLine(i);
                }
    
    
            }
    
            public void Test3() { 
                
                List<Student> students= new List<Student>() { 
                    new Student(1, "张三", 18),
                    new Student(2, "李四", 19),
                    new Student(3, "王五", 20),
                    new Student(4, "马六", 21)
                };
    
                Console.WriteLine($"总共有:{students.Count}位同学");
    
                foreach (Student student in students)
                {
                    student.Info();
                }
    
            }
        }
    }
    
    

    Test1方法中:List中存储的类型是string

    Test2方法中:List中存储的类型是int

    Test3方法中:List中存储的是实体类Student,List中常用的就是类型

  • 调用测试类:Program.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using ZJF.GenericTech._01_回顾List和Dictionary;
    
    namespace ZJF.GenericTech
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    
            static void Test1() {
                // 实例化
                TestListDict tt = new TestListDict();
                tt.Test1(); // 调用方法1
                tt.Test2(); 
                tt.Test3();
            }
        }
    }
    
    
  • 运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B8l63xWR-1684052960422)(D:\markdown\C#系列\CSharp高级进阶.assets\image-20230513105431066.png)]

Dictonary用法

Dictionary是一对Key Value ,一个key对应一个value

  • 字典用法

    public void Test4()
            {
                Dictionary<string, string> books = new Dictionary<string, string>() {
                    { "罗贯中","《三国演义》"},
                    { "施耐庵","《西游记》"},
                    { "曹雪芹", "《红楼梦》"},
                    { "吴承恩", "《水浒传》"}
                };
    
                Console.WriteLine($"一共有{books.Count}本书;");
    
                foreach (string author in books.Keys)
                {
                    Console.WriteLine($"{author}:{books[author]}");
                }
    
    
            }
    

7.2 引入泛型

我们需要一个计算器,这个计算器需要整数运算和浮点数运算,我们先不用泛型实现这个类:

  • Calc.cs

    通过重载来支持不同的数据类型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace ZJF.GenericTech._02_引入泛型
{
    public class Calc
    {
        static int Add(int x, int y) {
            return x + y;
        }

        static float Add(float x, float y) {
            return x + y;
        }

        static int Sub(int x, int y) {
            return x - y;
        }

        static float Sub(float x, float y) {
            return x - y;
        }
    }
}

这种写法需要写很多多余的代码。

7.3 泛型类、泛型方法、泛型返回值

定义泛型类:在定义的时候不需要明确类型,在使用的时候在明确类型。提高类的通用性。

定义一个泛型类

  • Stack.cs

    创建一个栈类,栈是一个后进先出的数据结构

    namespace ZJF.GenericTech._03_泛型基础
    {	
        // 泛型类
        internal class Stack<T>
        {
            // 私有成员变量,用于存储元素
            private T[] storage;
            // 私有成员变量,用于记录索引
            private int index;
            // 私有成员变量,用于确定容量
            private int capacity=10;
    
            public Stack() { 
                this.storage = new T[10];
                this.index = 0;
            }
    		// 压栈:泛型方法
            public void Push(T item) {
                if (this.storage.Length < this.capacity)
                {
                    this.storage[index] = item;
                    this.index++;
                }
                else {
                    throw new Exception("stack is full");
                }
                
            }
    		// 弹出:泛型返回值
            public T Pop() {
                if (this.storage.Length <= 0)
                {
                    T temp = this.storage[index - 1];
                    this.index--;
                    return temp;
                }
                else {
                    throw new Exception("空");
                }
                
            }
    
        }
    }
    
    
  • 测试类:

    		static void Test2()
            {
                Stack<int> intStack = new Stack<int>();
    
                intStack.Push(1);
                intStack.Push(2);
                intStack.Push(3);
                intStack.Push(4);
                intStack.Push(5);
                Console.WriteLine(intStack.Pop());
                Console.WriteLine(intStack.Pop());
                Console.WriteLine(intStack.Pop());
                Console.WriteLine(intStack.Pop());
                Console.WriteLine(intStack.Pop());
            }
    
  • 返回结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5tWabcF-1684052960422)(D:\markdown\C#系列\CSharp高级进阶.assets\image-20230513114809677.png)]

    代码运行结果与预期是相同的,最后被压栈的元素,最先一个被弹出。

7.4 default关键字

public class MyGeneric<T1, T2>
    {
        private T1 t1;
        private T2 t2;
        public MyGeneric() {
            t1 = default(T1);
            t2 = default(T2);
        }

        public void Info() {
            Console.WriteLine($"{this.t1}-{this.t2}");
        }
    }

如上面代码所示:一个泛型类,需要在构造方法中对两个私有变量进行初始化,值类型的默认值是0,引用类型的默认值是null

public class MyGeneric<T1, T2>
    {
        private T1 t1;
        private T2 t2;
        public MyGeneric() {
            t1 = 0;
            t2 = null;
        }

        public void Info() {
            Console.WriteLine($"{this.t1}-{this.t2}");
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dWtd312b-1684052960423)(D:\markdown\C#系列\CSharp高级进阶.assets\image-20230513120926770.png)]

vs 也很智能的给了提醒,因为在编译阶段并不知道t1和t2的类型,需要到运行阶段才能知道,default关键字的作用就是用使用时类型的初始值赋值给这个属性。

7.5 dynamic关键字

dynamic关键字让c#在编译阶段不检查变量的类型

7.6 泛型约束

  • 值类型约束

    where T : struct 表示类型参数必须是不可为 null 的值类型。

    // 值类型约束
    public class MyGenericList<T> where T : struct
    {
    	// 类成员... 
    }
    
    

    由于所有值类型都具有可访问的无参数构造函数,因此 struct 约束也表示 new() 约束,所以 struct 约束不能与 new() 约束和 unmanaged 约束一起使用。

  • 引用类型约束

    where T : class 表示类型参数必须是的引用类型。

    // 引用类型约束
    public class MyGenericList<T> where T : class
    {
    	// 类成员... 
    }
    
    

    此约束还应用于任何类、接口、委托或数组类型。 在可为 null 的上下文中,T 必须是不可为 null 的引用类型。

  • 可为空引用类型约束

    where T : class? 表示类型参数必须是可为 null 或不可为 null 的引用类型。

    // 引用类型约束
    public class MyGenericList<T> where T : class?
    {
    	// 类成员... 
    }
    
    

    此约束还应用于任何类、接口、委托或数组类型。

  • 不可为空约束

    where T : notnull 表示类型参数必须是不可为 null 的类型。

    // 引用类型约束
    public class MyGenericList<T> where T : notnull
    {
    	// 类成员... 
    }
    
    

    参数可以是不可为 null 的引用类型,也可以是不可为 null 的值类型。

  • 基方法约束

    where T : default 表示重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。

    // 引用类型约束
    public class MyGenericList<T> where T : default
    {
    	// 类成员... 
    }
    
    

    default 约束表示基方法,但不包含 class 或 struct 约束。

  • 非托管类型约束

    where T : unmanaged 表示类型参数必须是不可为 null 的非托管类型。

    // 引用类型约束
    public class MyGenericList<T> where T : unmanaged
    {
    	// 类成员... 
    }
    
    

    unmanaged 约束也表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。

  • 构造函数约束

    where T : new() 表示类型参数必须具有公共无参数构造函数。

    // 引用类型约束
    public class MyGenericList<T> where T : new()
    {
    	// 类成员... 
    }
    
    

    当构造函数约束与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。

  • 基类名约束

    where T :<基类名> 表示类型参数必须是指定的基类或派生自指定的基类。

    // 引用类型约束
    public class MyGenericList<T> where T :<基类名>
    {
    	// 类成员... 
    }
    
    

    在可为 null 的上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。

  • 可为空的基类名约束

    where T :<基类名>? 表示类型参数必须是指定的基类或派生自指定的基类。

    // 引用类型约束
    public class MyGenericList<T> where T :<基类名>?
    {
    	// 类成员... 
    }
    
    

    在可为 null 的上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。

  • 接口类型约束

    where T :<接口名称> 表示类型参数必须是指定的接口或实现指定的接口。

    // 引用类型约束
    public class MyGenericList<T> where T :<接口名称>
    {
    	// 类成员... 
    }
    
    

    可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中,T 必须是实现指定接口的不可为 null 的类型。

  • 可为空接口类型约束

    where T :<接口名称>? 表示类型参数必须是指定的接口或实现指定的接口。

    // 引用类型约束
    public class MyGenericList<T> where T :<接口名称>?
    {
    	// 类成员... 
    }
    
    

    可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。

  • 自定义约束

    where T : U 表示为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。

    // 引用类型约束
    public class MyGenericList<T> where T : U
    {
    	// 类成员... 
    }
    
    

    在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。

8 委托

8.1 引入委托

假设有两个类,一个类是AA,AA类中aa方法,一个类是BB,BB类中有bb方法,如果在AA类中调用bb方法,可以在BB类中定义一个属性位AA类的对象,但是如果在BB类中调用调用aa方法就很难实现了,这就是委托的应用场景。

委托相当于中介,我们通过中介找到房东,没有中介我们就找不到房东。

  • AA.cs

    public class AA
        {
            public void aa() {
                Console.WriteLine("我是AA类的aa方法");
            }
        }
    
  • BB.cs

    public class BB
        {
            public AA Aa { get; set; }
    
            public BB(AA a) {
                Aa = a;
            }
    
            public void bb() {
                Aa.aa();
                Console.WriteLine("我是BB类的bb方法");
            }
        }
    
  • Program.cs

    public class Program
        {
            static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    
            static void Test1() {
                AA a = new AA();
                BB b = new BB(a);
                b.bb();}
        }
    

8.2 使用委托的步骤

【1】声明委托类型(定义方法的原型),一般委托都要声明在类的外面,委托本身也是一种特殊的数据类型。

【2】编写具体方法(符合方法的原型)

【3】创建委托变量

【4】关联委托方法(将某一个或多个具体的方法和一个委托变量关联)

【5】使用委托变量(调用所关联的方法)

多路委托:一个委托变量关联了若干方法,当调用委托变量的时候,所关联的方法会按照关联的顺序依次执行。

  • Program.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using ZJF.DelegateTest._01_引入委托;
    using ZJF.DelegateTest._02_委托使用方法;
    
    namespace ZJF.DelegateTest
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Test2();
                Console.Read();
            }
           
            static void Test2() {
                DelegateTest1 dt1 = new DelegateTest1();
                //【3】创建委托变量
                SayHi sayHi = new SayHi(dt1.TestSayHi);
    
                //【4】关联委托方法(将某一个或多个具体的方法和一个委托变量关联)
                sayHi += dt1.TestSayHello; 
                //【5】使用委托变量(调用所关联的方法)
                sayHi();
            }
        }
    
        // 【1】声明委托类型(定义方法的原型),一般委托都要声明在类的外面,委托本身也是一种特殊的数据类型。
        public delegate void SayHi();
    }
    
    
  • DelegateTest.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.DelegateTest._02_委托使用方法
    {
        public class DelegateTest1
        {
            // 委托原型:public delegate void sayHi()
            // 委托实现:public void TestSayHi()
            //【2】编写具体方法(符合方法的原型)
            public void TestSayHi() {
                Console.WriteLine("hello1");
            }
    		
            public void TestSayHello() {
                Console.WriteLine("hello2");
            }
        }
    }
    
    

8.3 委托的基本应用

窗体之间的逆向传值

实现窗体之间的逆向传值,父窗体可以轻松把值交给子窗体,子窗体想要把值交给父窗体,那就需要使用委托了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13mj6Lm8-1684052960423)(D:\markdown\C#系列\CSharp高级进阶.assets\image-20230514151417074.png)]

  • FrmChild.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace ZJF.DelegateWindowTest
    {
        public partial class FrmChild : Form
        {
            public FrmChild()
            {
                InitializeComponent();
            }
    
            public TransferMsg SendMsg { get; set; }
    
    
    
            private void button1_Click(object sender, EventArgs e)
            {
                // 【5】调用委托变量
                SendMsg(this.childMsg.Text);
            }
        }
    }
    
    
  • FrmMain.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace ZJF.DelegateWindowTest
    {
        public partial class FrmMain : Form
        {
            public FrmMain()
            {
                InitializeComponent();
            }
    
            // 【2】编写具体方法(准备逆向调用的方法,和声明委托在同一个类中)
            public void RecvivedMsg(string value)
            {
                this.mainMsg.Text += value + "\r\n";
            }
            private void button1_Click(object sender, EventArgs e)
            {
                for (int i = 0; i < 3; i++)
                {
                    FrmChild frm = new FrmChild();
                    frm.Show();
                    // 【3】这一步很关键,将委托变量与执行函数进行了关联。
                    frm.SendMsg += RecvivedMsg;
                }
            }
    
            
        }
        //【1】声明委托类型(在哪个类中都可以,建议调用哪个方法,就在哪个类中)
        public delegate void TransferMsg(string value);
    }
    
    
  • 梳理一下逻辑

    1. 子窗体需要通过委托调用父窗体中的接收信息方法
    2. 调用方法同一个类中定义委托
    3. 子窗体中通过属性保存委托变量
    4. 调用属性进而调用委托后面的方法

父窗体将信息广播给子窗体

通过多路委托实现,多路委托能够很好的实现广播机制。

8.4 匿名方法

语法

  • 基础语法

    delegate (params){
        // 方法体
    }
    

    使用delegate关键字创建匿名方法,这个匿名方法可以用于直接关联委托变量。

  • 一般的委托用法

    namespace ZJF.Anonymous
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    		// 【3】委托背后的方法
            public static int AddCalc(int a, int b) {
                return a + b;
            }
    		
            public static void Test1()
            {
                // 【2】 关联委托
                Add addDelegate = new Add(AddCalc);
                Console.WriteLine( addDelegate(1,2) );
            }
        }
        //【1】定义一个委托
        public delegate int Add(int x, int y);
    
    }
    
    

    这里面需要注意的是,委托关联的函数都是需要定义的,我们使用匿名方法来简化这个程序、

  • 使用匿名方法简化定义

    namespace ZJF.Anonymous
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                Test1();
                Console.Read();
            }
    
            //public static int AddCalc(int a, int b) {
            //    return a + b;
            //}
    
            public static void Test1()
            {
                // 通过匿名方法直接关联委托变量,简化操作
                Add addDelegate = delegate(int x, int y) { return x + y; };
                Console.WriteLine( addDelegate(1,2) );
            }
        }
    
        public delegate int Add(int x, int y);
    
    
    }
    
    
    

    匿名方法好处:将具体方法和委托直接关联到一起,如果委托只需要一个方法的时候,匿名方法肯定是显得简单。

8.5 lambda表达式

Lambda表达式:在C#3.0出现的。使用这种表达的可以更简练的编写代码块。

CalculatorDelegate cal3 = (int a, int b) => { return a + b; };
CalculatorDelegate cal4 = (a, b) => a - b;

【1】在Lambda表达式中参数类型可以是明确类型,也可以是推断类型。
【2】如果是推断类型,则参数类型可以由编译根据上下文自动推断出来。
【3】运算符=> 读作goes to ,运算符左边输入参数(如果有),右边是表达式或语句块。
【4】表达式两种方式:

     (input  args)=>表达式   # 单一表达式
     (input  args)=>{ 语句1;语句2;语句3....}  # 多条语句

【5】Lambda表达式和匿名方法的比较

​ 第一、Lambda表达式本身就是匿名方法。
​ 第二、Lambda表达式允许不指明参数类型,但是匿名方法必须要明确。
​ 第三、Lambda表达式允许单一的表达式或多条语句组成,而匿名方法不允许单一表达式。

  • 测试

    namespace ZJF.Anonymous
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                Test2();
                Console.Read();
            }
    
            //public static int AddCalc(int a, int b) {
            //    return a + b;
            //}
    
            public static void Test1()
            {
                Add addDelegate = delegate(int x, int y) { return x + y; };
                Console.WriteLine( addDelegate(1,2) );
            }
    
            public static void Test2() {
    
                Add addDelegate = (a, b) => { return a + b; };  // 使用lambda表达式更简单
                Console.WriteLine(addDelegate(1,2));
            }
        }
    
        public delegate int Add(int x, int y);
    
    }
    
    

8.6 自定义泛型委托

1、为什要使用泛型委托?
普通委托在数据类型的限定上非常严格的。有时候我们需求变化,可能适应不了。

2、泛型委托定义:本质上和泛型方法是非常相似的,泛型委托关联的时候,可以是具体方法、匿名方法、也可以是lambda表达式。

  • MyGenericDelegate.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ZJF.Anonymous._01_自定义泛型委托
    {
        public class MyGenericDelegate
        {
            public static void Test1()
            {
                //【1】使用泛型委托关联具体方法
                CalculateAdd<int> calc = new CalculateAdd<int>(Add);
                Console.WriteLine(calc(1, 2));
    
                CalculateAdd<double> calc2 = new CalculateAdd<double>(Sub);
                Console.WriteLine(calc2(1.0, 2));
    
                // 【2】使用泛型委托关联匿名方法
                CalculateAdd<int> calc3 = delegate (int x, int y) { return x + y; };
                CalculateAdd<double> calc4 = delegate (double x, double y) { return x - y; };
                Console.WriteLine(calc3(1, 2));
                Console.WriteLine(calc4(1.0, 2));
    
    
                // 【3】 使用泛型委托关联lambda表达式
                CalculateAdd<int> calc5 = (x, y) => { return x + y; };
                CalculateAdd<double> calc6 = (x, y) => { return x - y; };
                Console.WriteLine(calc5(1, 2));
                Console.WriteLine(calc6(1.0, 2));
            }
    
            public static int Add(int x, int y)
            {
                return x + y;
            }
    
            public static double Sub(double x, double y)
            {
                return x - y;
            }
        }
    
        // 【1】自定义泛型委托
        public delegate T CalculateAdd<T>(T x, T y);
    }
    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

上下文感知的体素对比学习用于标签高效的多器官分割

文章目录 Context-Aware Voxel-Wise Contrastive Learning for Label Efficient Multi-organ Segmentation摘要本文方法有监督损失Context-Aware Contrastive Learning Loss for Unlabeled Voxels 实验结果 Context-Aware Voxel-Wise Contrastive Learning for Label Efficient…

GSMA-eSIM-官网规范说明

GSMA | eSIM Consumer and IoT Specifications - eSIM GSMA | eSIM Consumer and IoT Specifications - eSIMhttps://www.gsma.com/esim/esim-specification/ 架构、测试、技术规范 3.0版本 测试套件 EID的定义和分配 GSMA EID Definition and Assignment 合规性规范 自我评…

第十四届蓝桥杯青少组模拟赛Python真题 (2023年2月12日),包含答案

第十四届蓝桥杯青少组模拟赛Python真题 (2023年2月12日) 一、选择题 第 1 题 单选题 关于Python3.10,下列选项描述正确的是 ( )。 答案:B 第 2 题 单选题 下列数据类型中,哪一个是不可变的?() 答案:B 第 3 题 单选题 以下关于函数参数描述正确的是 ()。 答案:D 第 …

【JAVAEE】文件操作——IO

目录 &#x1f48b;1. 冯诺伊曼体系 &#x1f437;2. 内存与外存的区别 ✨3. 文件 &#x1f381;3.1 认识文件 ✌3.2 文件的管理 &#x1f373;3.3 文件路径 &#x1f383;3.4 文件的保存 &#x1f451;3.4.1 文本文件 &#x1f4f7;3.4.2 二进制文件 &#x1f380;3.5 文件系…

Python基本数据类型之一——set(集合)

Python基本数据类型之一——set(集合) 一、python集合定义 集合(set)是一个无序不重复元素的序列。基本功能是进行成员关系测试和删除重复元素。 二、创建方式 在Python中&#xff0c;创建集合有两种方式&#xff1a; 一种是用一对大括号将多个用逗号分隔的数据括起来。 另一种…

Vue3-黑马(九)

目录&#xff1a; &#xff08;1&#xff09;vue3-antdv-删除选中 &#xff08;2&#xff09;vue3-进阶-antdv-增改 &#xff08;3&#xff09;vue3-进阶-antdv-增改2 &#xff08;1&#xff09;vue3-antdv-删除选中 我们在表格中在加一列&#xff0c;做一个复选框&#xff…

【一起啃书】《机器学习》第六章 支持向量机

文章目录 第六章 支持向量机6.1 间隔和支持向量6.2 对偶问题6.3 核函数6.4 软间隔与正则化6.5 支持向量回归6.6 核方法6.7 一些问题 第六章 支持向量机 6.1 间隔和支持向量 给定训练样本集 D { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x m , y m ) } , y i ∈ { − 1 , …

【YOLO系列】--YOLOv4超详细解读/总结

YOLOv4&#xff08;YOLOv4: Optimal Speed and Accuracy of Object Detection&#xff09;&#xff08;原文&#xff0b;解读/总结&#xff0b;翻译&#xff09; 系列文章&#xff1a; YOLOv1论文解读/总结_yolo论文原文_耿鬼喝椰汁的博客-CSDN博客 YOLOv2论文解读/总结_耿鬼…

[免疫学]抗原递呈详解

目录 &#x1f9eb;1. 抗原递呈是什么 &#x1f9eb;1.1 MHCⅠ类分子 &#x1f9eb;1.2 MHCⅡ类分子 &#x1f9eb;1.3 MHCⅠ类分子 分子介导的抗原递呈 &#x1f9eb;1.4 MHCⅡ类分子 分子介导的抗原递呈 &#x1f9eb;1.5 抗原递呈细胞 &#x1f9eb;1.5.1 激活的树突状…

初识Linux篇:第三篇

初识Linux篇&#xff1a;第三篇 初识Linux篇&#xff1a;第三篇1.Linux中常见的通配符1.1*号通配符1.2 &#xff1f;通配符1.3[ ]通配符1.4{ }通配符 2.man指令3.cp指令4.mv指令5.alias指令6. echo指令7.cat指令8.more指令9.less指令10.head与tail指令11.在Linux中写代码 总结 …

DHCP中继负载均衡使用原理

DHCP常用四种报文 基于UDP67服务端&#xff0c; 68端口客户端 discover 广播 offer 单播 rquest 广播 ack 单播 DHCP 50%时间续租时request报文为单播报文 DHCP 87.5%时间请求重新绑定IP时request报文为广播报文 DHCP的另类配置方案&#xff1a; 负载均衡&#xff1a; ip re…

【Python_Pandas】reset_index() 函数解析

【Python_Pandas】reset_index函数解析 文章目录 【Python_Pandas】reset_index函数解析1. 介绍2. 示例2.1 参数drop2.2 参数inplace2.3 参数level2.4 参数col_level2.5 参数col_fill 参考 1. 介绍 pandas.DataFrame.reset_index reset_index(levelNone, dropFalse, inplaceF…

【AI大模型智慧办公】用《文心一言》1分钟写一篇博客简直yyds

文章目录 前言文心一言是什么文心一言可以做什么文心一言写博客申请体验写在最后 前言 当今社会&#xff0c;博客已成为了许多人分享观点、知识和经验的重要平台。用文心一言写博客是将自己的思考、想法和经验以文字的形式呈现出来&#xff0c;让更多人了解自己。通过写博客&a…

【Python入门】Python循环语句(while循环的基础语法)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

【计网】第三章 数据链路层(4)局域网、以太网、无线局域网、VLAN

文章目录 3.6-1 局域网基本概念和体系结构一、局域网二、局域网基本概念和体系结构三、局域网传输介质四、局域网介质控制方法五、局域网分类六、IEEE 802 标准七、MAC 子层和 LLC 子层总结&#xff1a; 3.6-2 以太网一、以太网概述二、以太网提供无连接、不可靠的服务三、以太…

RabbitMQ启动失败

首先&#xff0c;你得先确认的你得Erlang的版本和RabbitMQ的版本是否能相互兼容&#xff0c;如果不能&#xff0c;请先下载对应的Erlang版本或者RabbitMQ版本。 1. 在Java中使用了RabbitMQ发现不停的尝试连接&#xff0c;却连不上的问题 先在cmd下输入erl&#xff0c;看一下E…

EXCEL比较麻烦零碎的日期数据的处理,数字转日期,日期转数字

1 有时候想输入日期&#xff0c;但会被EXCEL自动识别为数字 原因是 有的EXCEL版本会处理&#xff0c;有的则不会自动处理为日期。也可能&#xff0c;单元格因为各种原因被提前设置了格式&#xff0c;比如常规等。还有的原因是因为EXCEL从xml等其他文件里导入的日期时间等数据本…

大数据Doris(二十):Doris的Bitmap索引介绍

文章目录 Doris的Bitmap索引介绍 一、Bitmap位图索引原理 二、Bitmap位图索引语法 三、注意事项 Doris的Bitmap索引介绍 一、Bitmap位图索引原理 bitmap&#xff0c;位图&#xff0c;是一种数据结构&#xff0c;即bit的集合&#xff0c;每一个bit记录0或者1&#xff0c;代…

EPLAN2022 3D宏文件创建

我们经常使用导入3D模型文件创建宏文件。但有时候导入的3D文件缺少了一些细节或者变形&#xff0c;因此在导入3D文件之前&#xff0c;需要设置一下细节清晰度。 1. 设置导入3D图的清晰度 选择菜单栏中的【文件】➡【设置】 选择设置的项目【新项目&#xff08;4&#xff09;】…

【Linux内核解析-linux-5.14.10-内核源码注释】信号量semaphore机制

信号量实现方法 信号量机制是一种用于控制并发访问的同步机制&#xff0c;常用于多进程或多线程之间的协调。在Linux内核中&#xff0c;信号量机制是通过struct semaphore结构体来实现的。 每个semaphore结构体包含一个计数器和一个等待队列&#xff0c;它们用于跟踪当前可用…