C# 入门学习教程(三)
文章目录
- 一、字段、属性、索引器、常量
- 1.字段
- 2.属性
- 2.1 什么是属性
- 2.2 属性的声明
- 2.3 属性与字段的关系
- 3 索引器
- 4. 常量
- 二、传值 输出 引用 数组 具名 可选参数,扩展方法
- 2.1 传值参数
- 2.1.1 值类型 传参
- 2.1.2 引用类型 传参
- 2.2 引用参数
- 2.2.1 引用参数-值类型 传参
- 2.2.2 引用参数 - 引用类型 传参
- 2.3 输出参数
- 2.3.1 输出参数-值类型
- 2.3.2 输出参数-引用类型
- 2.4 数组参数 (params)
- 2.5 具名参数
- 2.6 可选参数
- 2.7 扩展方法(this参数)
- 扩展方法
- LINQ 方法
- 2.8 各种参数的使用场景总结
一、字段、属性、索引器、常量
1.字段
-
什么是字段
- 字段(field)是一种表示与对象或类型(类与结构体)关联的变量"字段是类型的成员,旧称“成员变量
- 与对象关联的字段亦称“实例字段”
- 与类型关联的字段称为“静态字段””,由static修饰
-
字段的声明
- 参见C#语言定义文档
- 尽管字段声明带有分号但它不是语句
- 字段的名字一定是名词
-
字段的初始值
- 无显式初始化时,字段获得其类型的默认值,所以字段“永远都不会未被初始化”
- 实例字段初始化的时机–对象创建时
- 静态字段初始化的时机–类型被加载(load )时
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){//实例化一个学生Student student = new Student();student.Age = 10;student.Score = 100;Console.WriteLine(student.Score);//实例化100个学生,求平均年龄和平均分List<Student> studentList = new List<Student>();for (int i = 0; i < 100; i++) { Student stu = new Student();stu.Age = 25;stu.Score = i+10;studentList.Add(stu);}int totalAge = 0;int totalScore = 0;foreach (var stu in studentList){totalAge += stu.Age;totalScore += stu.Score;}Student.AverageAge = totalAge/Student.Amount;Student.AverageScore = totalScore/Student.Amount;Student.ReportAverageScore();Student.ReportAverageAge();}}class Student{public int Age;public int Score;// 字段初始化器public int AgeInit = 30;//平均年龄public static int AverageAge;//平均分public static int AverageScore;//静态字段的初始化器public static int Amount = 0;public Student(){Student.Amount++;}public static void ReportAverageAge(){Console.WriteLine(Student.AverageAge);}public static void ReportAverageScore(){Console.WriteLine(Student.AverageScore);}public void ReportAmount(int score){Console.WriteLine(Student.Amount);}} }
-
只读字段
- 实例只读字段
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne(35);Console.WriteLine( studentOne.ID);//错误:给只读字段赋值将会报错studentOne.ID = 100;}}class StudentOne{//学生id设置为只读字段public readonly int ID;public string Name;public int Age;//只读字段实例化构造器public StudentOne(int Id){//只有在实例化的时候才能赋值this.ID = Id;}} }
- 静态只读字段
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){Console.WriteLine(Brush.DefaultColor.Red);Console.WriteLine(Brush.DefaultColor.Green);}}struct Color{public int Red;public int Green;public int Blue;}class Brush{//声明一个静态只读字段// public static readonly Color DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 };//静态只读字段构造器public static readonly Color DefaultColor;static Brush(){Brush.DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 };}} }
2.属性
2.1 什么是属性
- 属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态
- 属性是字段的自然扩展,
- 从命名上看,field更偏向于实例对象在内存中的布局,property更偏向于反映现实世界对象的特征
- 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
- 对内:保护字段不被非法值“污染”属性由Get/Set方法对进化而来
- 又一个“语法糖”–属性背后的秘密
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace PropertyExample{class PropertyExample{static void Main(string[] args){//字段的定义使用try{Student student = new Student();student.setAge(30);Student student2 = new Student();student2.setAge(50);Student student1 = new Student();student1.setAge(80);int avgAge = (student.getAge() + student2.getAge() + student1.getAge()) / 3;Console.WriteLine(avgAge);}catch (Exception ex){Console.WriteLine(ex.Message);}//属性的使用try{Student student = new Student();student.Age = 25;Student student2 = new Student();student2.Age = 40;Student student1 = new Student();student1.Age = 50;int avgAge = (student.Age + student2.Age + student1.Age) / 3;Console.WriteLine(avgAge);}catch (Exception ex){Console.WriteLine(ex.Message);}}}class Student{private int age;//原始的字段获取和赋值public int getAge(){return this.age;}public void setAge(int age){if (age > 100 || age < 0){throw new Exception("Age has Error");}else{this.age = age;}}//演变成属性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}}}
反编译分析:
输入: ildasm
, 打开反编译器
打开文件,找到对应的dll文件打开,如下:
2.2 属性的声明
- 完整声明–后台(back)成员变量与访问器(注意使用code snippet和refactor工具)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){//实例属性try{Student student = new Student();student.Age = 25;Console.WriteLine(student.Age );}catch (Exception ex){Console.WriteLine(ex.Message);}//静态属性try{Student.Amount = 80;Console.WriteLine(Student.Amount);}catch (Exception ex){Console.WriteLine(ex.Message);}}}class Student{//实例属性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}//静态属性private static int amount;public static int Amount{get { return amount; }set{if (value > 0){amount = value;}else{throw new Exception("amount is error");}}}}
}
- 简略声明–只有访问器(查看IL代码 )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Student student3 = new Student();student3.ageInit = 50;Console.WriteLine(student3.ageInit);}}class Student{public int ageInit { get; set; }}
}
- 动态计算值的属性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Student student5 = new Student();student5.Age = 50;Console.WriteLine( student5.CanWork);}}class Student{//年龄属性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}//能否工作属性public bool CanWork{get{if (this.age > 18){return true;}else{return false;}}}}
}
-
注意实例属性和静态属性
-
属性的名字一定是名词
-
只读属性–只有getter没有setter
- 尽管语法上正确,几乎没有人使用“只写属性”,因为属性的主要目的是通过向外暴露数据而表示对象/类型的状态
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){//ageInit 是只读属性,不能赋值Student student3 = new Student();Console.WriteLine(student3.ageInit);//score 属性不允许外部赋值,只能内部赋值Student student4 = new Student();student4.setScore();Console.WriteLine(student4.score);}}class Student{//只读属性,没有set方法public int ageInit { get; }//有set方法,但是是私有的,只能内部进行赋值public int score { get;private set;}// 内部赋值public void setScore(){this.score = 100;}}
}
2.3 属性与字段的关系
- 一般情况下,它们都用于表示实体(对象或类型)的状态
- 属性大多数情况下是字段的包装器(wrapper)
- 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的
3 索引器
- 什么是索引器
- 索引器 (indexer) 是这样一种成员:它使对象能够用与数组相同的方式(即使用下标)进行索引
- 索引器的声明
- 参见C#语言定义文档
- 注意:没有静态索引器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Studentcj student6 = new Studentcj();var aa = student6["xiaoming"];Console.WriteLine(aa==null);student6["xiaoming"] = 60;Console.WriteLine(student6["xiaoming"]);}}class Studentcj{//定义一个集合private Dictionary<string,int> scoreDictionary = new Dictionary<string,int>();public int? this[string subject]{get {// 检测是否存在,存在则返回具体值,不存在返回nullif (this.scoreDictionary.ContainsKey(subject)){return this.scoreDictionary[subject];}else{return null;}}set{// 设置的值为null,直接异常if (value.HasValue == false){throw new Exception("值不能为空");}//检测是否有值,有值则覆盖,没有则新增if (this.scoreDictionary.ContainsKey(subject)){this.scoreDictionary[subject] = value.Value;}else{this.scoreDictionary.Add(subject, value.Value);}}}}
}
4. 常量
-
什么是常量
-
常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
-
常量隶属于类型而不是对象,即没有“
实例常量
”- “实例常量”的角色由只读实例字段来担当
-
注意区分成员常量与局部常量
namespace consetExample {class Program{static void Main(string[] args){// 局部常量const int Age = 35; Console.WriteLine(baidu.url);}}class baidu{//成员常量public const string url = "www.baidu.com";} }
-
-
常量的声明
-
各种“只读”的应用场景
- 为了提高程序可读性和执行效率–常量
- 为了防止对象的值被改变一-只读字段
- 向外暴露不允许修改的数据–只读属性(静态或非静态),功能与常量有一些重叠
- 当希望成为常量的值
其类型
不能被常量声明接受时(类/自定义结构体
)–静态只读字段- 常量的类型只能是普通的值类型,不能是类或者自定义结构体
二、传值 输出 引用 数组 具名 可选参数,扩展方法
2.1 传值参数
2.1.1 值类型 传参
值类型传参 是指将值类型(如 int、struct、enum 等)的变量作为参数传递给方法。值类型传参的核心特点是 传递的是变量值的副本,因此方法内部对参数的修改不会影响原始变量。
- 副本创建:当值类型变量作为参数传递时,系统会创建一个与原始值相同的副本。
- 独立作用域:方法内部操作的是副本,原始变量不受影响。
- 内存分配:副本在方法调用栈上分配独立内存。
值类型
传参 流程如图:
- 代码实例:
namespace ParametersExample
{class Program{static void Main(string[] args){int num = 100;Student student = new Student();student.AddOne(num);Console.WriteLine(num);}}class Student{public void AddOne(int num){num ++;Console.WriteLine(num);}}
}
-
定义一个值类型的变量 num
-
传值进方法后,方法内的变量 num 会变更
-
方法外的变量 num 不会随着值参数的变化而变化
-
调试结果:
2.1.2 引用类型 传参
引用类型传参 是指将引用类型(如 class、array、string、interface 等)的变量作为参数传递给方法。引用类型传参的核心特点是 传递的是对象引用的副本
,而非对象本身
- 传递时创建引用的副本(内存地址的复制),而非对象的副本。
- 原始引用和副本引用指向同一个对象实例。
-
引用类型传参,
创建新对象
重设引用副本:仅影响副本,不影响原始引用。
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne();studentOne.Id = 100;studentOne.Name = "张三";SomeMethod(studentOne);Console.WriteLine(studentOne.Id);//打印对象的 HashCode,以及属性Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void SomeMethod(StudentOne student){student = new StudentOne();student.Id = 1;studentOne.Name = "张三";Console.WriteLine(studentOne.Id);//打印对象的 HashCode,以及属性Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
- 定义StudentOne 类,属性ID,Name
- 初始化一个studentOne 对象,属性赋值,将对象传进SomeMethod 方法
- SomeMethod 方法内会初始化一个新对象 student ,并赋值
- 打印方法内外变量的值,虽然名称都是张三,但是实例却不是同一个
-
引用类型传参,
操作对象,不创建新对象
修改对象属性 / 状态:影响原始对象。
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne();studentOne.Id = 100;studentOne.Name = "张三";SomeMethodOne(studentOne);Console.WriteLine(studentOne.Id);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void SomeMethodOne(StudentOne studentOne){studentOne.Id = 2; // 方法的副作用studentOne.Name = "李四";Console.WriteLine(studentOne.Id);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
SomeMethodOne
方法内操作对象,重新赋值,方法内外的变量会同时变化.- 方法内进行更新属性的操作,称为方法的副作用,较少见,需要
避免使用
。
2.2 引用参数
引用参数(Reference Parameters) 允许方法直接操作调用者提供的变量,而非创建参数的副本。通过 ref
关键字声明引用参数,使方法能够修改原始变量的值或重设其引用。
这是一种强大的参数传递机制,适用于需要双向数据交换的场景。
- 在方法定义和调用时均需显式使用 ref 关键字。
- 传递变量的内存地址(而非值或引用的副本)。
- 方法内部对参数的任何修改直接反映到原始变量
- 实参必须是已初始化的变量,不能是常量或表达式
2.2.1 引用参数-值类型 传参
引用参数(ref)与值类型传参
的组合允许方法直接操作调用者提供的值类型变量
,而非创建其副本。
这种方式结合了 ref 关键字的双向修改能力和值类型的内存特性,常用于需要高效修改值类型变量的场景。
namespace ParametersExample
{class Program{static void Main(string[] args){int x = 100;addNum(ref x);Console.WriteLine(x);}public static void addNum(ref int x){x++;Console.WriteLine(x);}}
}
2.2.2 引用参数 - 引用类型 传参
引用参数(ref)与引用类型
传参 的组合允许方法直接操作调用者提供的引用类型变量
,包括修改对象状态或重设引用本身。
这种方式结合了 ref 关键字的双向修改能力和引用类型的内存特性,常用于需要更灵活控制对象引用的场景。
- 引用参数 - 引用类型 , 创建新对象
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne() { Id=1,Name="李四"};Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);Console.WriteLine("=============================");IWantSideEffect(ref studentOne);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void IWantSideEffect(ref StudentOne stu){stu = new StudentOne(){Id = 1, Name="张三"};Console.WriteLine("{0},{1}",stu.GetHashCode(),stu.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
- 引用参数 - 引用类型 , 不创建新对象,改变对象值
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne() { Name="李四"};Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);Console.WriteLine("=============================");IWantSideEffect(ref studentOne);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void IWantSideEffect(ref StudentOne stu){stu.Name = "李四";Console.WriteLine("{0},{1}",stu.GetHashCode(),stu.Name);}}class StudentOne{public string Name { get; set; }}
}
2.3 输出参数
输出参数(Output Parameters) 允许方法通过参数返回多个值,而不仅仅依赖于返回值。通过 out
关键字声明输出参数,使方法能够将计算结果赋值给调用者提供的变量。这是一种简洁且类型安全的多返回值
解决方案。
- 在方法定义和调用时均需显式使用
out
关键字。 - 传递变量的引用(类似 ref),但
无需初始化
- 方法
必须
在返回前为所有 out
参数赋值 - out 参数在方法
调用后
才被视为已初始化。
2.3.1 输出参数-值类型
namespace ParametersExample
{class Program{static void Main(string[] args){double y = 0;string x = "100";DoubleParser doubleParser = new DoubleParser();bool b = doubleParser.tryParser(x, out y);if (b){Console.WriteLine(y+1);}else{Console.WriteLine(y);}}}class DoubleParser{public bool tryParser(string x, out double y){try{y = double.Parse(x);return true;}catch (Exception e){y = 0;return false;}}}
}
2.3.2 输出参数-引用类型
namespace ParametersExample
{class Program{static void Main(string[] args){StudentFactory studentFactory = new StudentFactory();StudentOne studentOne = new StudentOne();bool res = studentFactory.CreateStu("张三", 35, out studentOne);if (res){Console.WriteLine("{0},{1}", studentOne.Age, studentOne.Name);}}}class StudentOne{public string Name { get; set; }public int Age { get; set; }}class StudentFactory(){public bool CreateStu(string Name, int Age, out StudentOne student){student = null;if (string.IsNullOrEmpty(Name)){return false;}if (Age > 100){return false;}student = new StudentOne();student.Name = Name;student.Age = Age;return true;}}
}
2.4 数组参数 (params)
使用 params 关键字允许方法接收任意数量的参数(包括零个),并在方法内部将其视为数组处理。
- params 类型[] 参数名
- 必须是形参列表中最后一个,有params 修饰
- 可传递数组,或直接传递多个参数值
namespace ParametersExample
{class Program{static void Main(string[] args){int[] ints = new int[] { 1, 2, 3 };//传递数组int num = countSum(ints);//方法的数组参数用params修饰后,传值变简单int num2 = countSum(1, 2, 3);Console.WriteLine("{0},{1}", num, num2);}// 使用 params 关键字的方法static int countSum(params int[] array){int sum = 0;foreach (int i in array){sum += i;}return sum;}}
}
- string.Split 方法演示:
namespace ParametersExample
{class Program{static void Main(string[] args){string aa = "张三, 李四; 王五# 赵六";string[] sss = aa.Split(',', ';', '#');foreach (var item in sss){Console.WriteLine(item);}}}
}
2.5 具名参数
具名参数(Named Arguments) 允许你在调用方法时通过参数名称而不是位置来指定参数值。这使得代码更具可读性,特别是在处理具有多个参数的方法时,还可以跳过可选参数或按任意顺序传递参数。
- 通过 参数名: 值 的语法指定参数值。
- 可以不按方法定义的参数顺序传递参数
- 可以选择性地为可选参数赋值,未指定的参数使用默认值。
- 参数位置不再受约束
namespace ParametersExample
{class Program{static void Main(string[] args){// 使用具名参数(顺序可任意)printInfo(age: 35,name:"张三");}static void printInfo(string name,int age){Console.WriteLine("{0},{1}",name,age);}}
}
2.6 可选参数
可选参数是方法定义里能够设定默认值的参数。调用方法时,这些参数可传值也能不传。
- 定义方法时,通过赋值运算符(=)给参数赋予默认值
- 可选参数必须放在必选参数的后面
- 调用方法时,可以不传入可选参数的值,此时会使用默认值;也可以按顺序传值或者通过命名参数传值。
namespace ParamsExample
{internal class Program{static void Main(string[] args){showParams();}static void showParams(string Name="张三",int Age=35){Console.WriteLine("{0},{1}",Name,Age);}}
}
2.7 扩展方法(this参数)
扩展方法
扩展方法(Extension Methods) 允许你向现有类型 “添加” 方法,而无需创建新的派生类型、修改原始类型或使用复杂的装饰器模式。扩展方法使用 this 关键字修饰第一个参数,使其看起来像被扩展类型的实例方法。这是一种强大的语法糖,常用于增强框架类型或第三方库的功能。
- 语法
- 必须定义在静态类中。
- 第一个参数必须使用 this 关键字修饰。
- 通常为公共静态方法。
- 调用方式
- 像实例方法一样调用(无需显式传入第一个参数)。
- 需要引入扩展方法所在的命名空间。
- 优先级
- 实例方法优先于扩展方法。
- 多个扩展方法冲突时需显式指定类型调用。
namespace ParamsExample
{internal class Program{static void Main(string[] args){string str = "";Console.WriteLine(str.IsNullOrWhitespace());}}// 静态类:包含扩展方法public static class StringExtensions{//扩展方法:判断字符串是否为空或空白public static bool IsNullOrWhitespace(this string str){if (string.IsNullOrEmpty(str)){return true;}if (str.Trim().Length == 0){return true;}return false;}}
}
LINQ 方法
LINQ(Language Integrated Query) 提供了一组标准查询方法,用于对各种数据源(如集合、数组、数据库等)进行高效的查询、筛选、投影和聚合操作。
这些方法既可以通过 ** 方法语法(Method Syntax)直接调用,也可以通过查询语法(Query Syntax)** 以类似 SQL 的形式表达。
namespace ParamsExample
{internal class Program{static void Main(string[] args){List<int> list = new List<int>() { 11, 19, 20, 30,15, 19 };//访问静态方法bool x = AllGreaterThanTen(list);Console.WriteLine(x);//筛选大于10的bool y = list.All(i => i > 10);Console.WriteLine(y);}public static bool AllGreaterThanTen(List<int> ints){foreach (var item in ints){if (item > 10){continue;}else{return false;}}return true;}}
}
2.8 各种参数的使用场景总结
- 传值参数:参数的默认传递方式
- 输出参数:用于除返回值外还需要输出的场景
- 引用参数:用于需要修改实际参数值的场景
- 数组参数:用于简化方法的调用
- 具名参数:提高可读性
- 可选参数:参数拥有默认值
- 扩展方法(this参数):为目标数据类型“追加”方法