#Region 这些代码实现了折叠功能
#EndRegion
占位符
int n1 = 10;
int n2 = 20;
int n3 = 30;
Console.WriteLine("first number:{0};second number:{1};third number:{2}",n1,n2,n3);//10 20 30
Console.WriteLine("first number:{1};second number:{0};third number:{2}", n1, n2, n3);//20 10 30
int a = 10;
int b = 3;
double c = a * 1.0 / b;
Console.WriteLine("{0:0.00}",c); //这个占位符靓哦,这样就不会输出3.33333333...而是输出3.33
转义符和@符号
转义符指的是一个""+一个特殊的字符,组成了一个具有特殊意义的字符,
@符号:1.取消在字符串中的转义作用,2.将字符串按照原格式输出
两种数据类型不兼容我们可以用datatype.Parse和Convert.Todatatype来进行转换,例如:string s="123"; int a=int.Parse(s); int b=Convert.ToInt32(s);
string s = "123aaa";
int n1 = int.Parse(s);//抛出异常
int n2 = Convert.ToInt32(s);//抛出异常
int n;
bool b1 = int.TryParse(s,out n);//不抛出异常,b1是false,n=0
bool b2 = int.TryParse("123", out n);//不抛出异常,b1是true,n=123
continue 跳出本次循环,break跳出整个循环,return跳出方法体
enum
namespace ConsoleApp4
{
//一般将枚举声明到命名空间的下面,类的上面,表示这个命名空间里的所有类都可以使用这个枚举
public enum Gender
{
Male,
Female,
}
class Program
{
static void Main(string[] args)
{
char gender1 = '男';
string gender2 = "man";
string gender3 = "爷们";
//由于开发者的个性,对性别有不同的定义,将多个开发者的代码整合到一块时可能会出现错误,所以枚举起到了规范代码开发的作用
Gender gender = Gender.Male;
}
}
}
enum与int与string的关系
namespace ConsoleApp1
{
public enum Season//枚举类型可以和int类型互换,枚举类型和int类型是兼容的
{
spring,
summer,
autumn,
winter
}
class Program
{
static void Main(string[] args)
{
int n = (int)Season.summer;//枚举->int n=1
string s = Season.spring.ToString();//枚举->string s="spring"
Season season = (Season)2;//int->枚举 season=autumn
string s1 = "2";//s1="2"与s1="spring",输出的值是一样的
Season season1 = (Season)Enum.Parse(typeof(Season), s1);//string->枚举 season1=autumn
}
}
}
C#中的关键字
public
同一程序集中的任何其他代码或引用该程序集的其他程序集都可以访问该类型或成员。
private
只有同一类或结构中的代码可以访问该类型或成员。
protected
只有同一类或结构或者此类的派生类中的代码才可以访问该类型或成员。
internal
同一程序集中的任何代码都可以访问该类型或成员,但其他程序集中的代码不可以。
命名空间下元素的默认访问修饰符:命名空间下只能使用两种访问修饰符public和internal。命名空间下的枚举和结构体,类,接口默认为internal
各类型中成员的访问修饰符:
类中所有的成员,默认均为private,当然也可以修改成其它的访问修饰符。
接口的成员默认访问修饰符是public,也不可能是其他访问修饰符。
枚举类型成员默认public,也不可能是其他访问修饰符。
委托,默认的是internal 。
const
可以使用const关键字声明常量字段和局部常量。
常量字段和局部常量不是变量,不可以被更改。
常量可以是数字,字符串,布尔值,空引用。
从C# 10开始,如果使用的所有表达式是常量的,内插字符串也可以是常量的,如下
const string Language = "C#";
const string Platform = ".NET";
const string Version = "10.0";
const string FullProductName = $"{Platform} - Language: {Language} Version: {Version}";
局部常量和常量字段的初始化器必须是可以隐式转换成目标类型的常量表达式。
常量表达式是在编译时就可以计算的表达式,所以引用类型的常量其值只可能是字符串类型或者空引用类型。
在常量声明中不允许使用static修饰符。
const和readonly的区别:1.常量字段只能在声明的时候被初始化,只读字段既可以在声明的时候被初始化,也可以在构造函数中被初始化。所以只读字段可以有不同的值,这取决于其使用的构造函数。2.常量字段是编译时常量,只读字段可以用于运行时常量,如:public static readonly uint l1=(uint)DateTime.Now.Ticks。3.readonly只能修饰字段,const既可以修饰字段也可以修饰局部变量。
static
使用static修饰符声明一个静态成员,该成员属于类型本身,而不是指定的对象
在类、接口、结构中你可以给字段、属性、方法、事件、构造器、操作符添加static修饰符,static修饰符不能用于索引器和终结器
C# 9.0开始,你可以给Lambda表达式,匿名函数添加static修饰符。一个静态的匿名函数和Lambda表达式不能捕获局部变量或者实例状态。(这句是什么意思?)
一个常量隐含地是静态成员
public class MyBaseC
{
public struct MyStruct
{
public static int x = 100;
}
}
为了引用上面静态成员x,使用完全限定名称,如下
Console.WriteLine(MyBaseC.MyStruct.x);
虽然一个类的实例包含该类的所有实例字段的单独副本,但一个静态字段只有一个副本
如果static关键字被用于类,那么该类的所有成员都是静态的
类、接口、静态类可以有静态构造器,在程序启动和实例化类的某个节点调用静态构造器。
结构namespace ConsoleApp1
{
//结构可以帮助我们一次声明多个不同类型的变量
struct Student//结构和枚举的默认访问权限为public
{
public string _name;//结构成员默认访问权限为private,枚举默认访问权限为public
public int _age; //name,age,gender是字段,光标放到上面会有提示,变量不可以用修饰符修饰,字段可以
public Gender _gender;
} //为了区分字段与变量字段前会加"_"
enum Gender
{
male,
female
}
class Program
{
static void Main(string[] args)//如果一个学校有2000个学生需要声明6000个变量,有了结构只需声明2000个变量
{
Student student1 = new Student();
student1._name = "tang-san";
student1._age = 16;
student1._gender = Gender.male;//枚举与结构本质上都是变量
}
}
}
静态与非静态的区别
类加载的时候,所有的静态类及其静态成员就会被创建在“静态存储区”里面,一旦创建直到程序退出,才会被回收
静态类中只允许有静态成员,不允许有实例成员
静态成员是属于类的,实例成员是属于对象的
静态成员必须使用类名去调用,而实例成员使用对象名调用
静态函数中只能访问静态成员,不能访问实例成员
实例函数中既可以访问静态成员,也可以访问实例成员
如果把一个类当做“工具类”去使用,这个时候可以考虑将其写成静态类,静态类在整个项目中资源共享。
namespace ConsoleApp1
{
class Program
{
public static int n = 0;
public Program()
{
n = 1;
}
static Program()//静态构造函数只执行一次,所以静态构造函数中不允许出现访问修饰符
{
n = 2;
}
static void Main(string[] args)
{
Console.WriteLine(n);//n=2,因为在类加载时静态构造函数执行了
Program program = new Program();
Console.WriteLine(n);//n=1,因为实例化时构造函数执行了
}
}
}
out参数
在一个方法中返回多个相同类型的值时可以考虑使用数组,当返回多个不同类型的值时,就要使用out
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] nums = new int[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int max;
int min;
string result;
FinderValues(nums,out max,out min,out result);
}
static void FinderValues(int[] nums,out int max,out int min,out string result)
{
min = nums[0];
max = nums[0];
for(int i = 0; i < nums.Length; i++)
{
if (min > nums[i]) min = nums[i];
if (max < nums[i]) max = nums[i];
}
result = "找值完成";
}
}
}
ref参数
能够将一个变量带入一个方法中更改,改变完成后再将改变后的变量带出方法
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int salary = 5000;
JiangJin(ref salary);
Console.WriteLine(salary);//输出5500
FaKuan(ref salary);
Console.WriteLine(salary);//输出5000
}
static void JiangJin(ref int salary)
{
salary += 500;
}
static void FaKuan(ref int salary)
{
salary -= 500;
}
}
}
params可变参数
将实参列表中跟可变参数数组一致的元素都当做数组的元素去处理。
params必须是形参列表中的最后一个元素。
class Program
{
static void Main(string[] args)
{
string name = "xiao-wu";
int[] scores = new int[] { 90, 94, 92 };
TotalScore(name, scores);
//TotalScore(name, 90, 94, 92 );//这样也可以哦!
Console.ReadKey();
}
static void TotalScore(string name,params int[] scores)
{
int sum = 0;
for(int i = 0; i < scores.Length; i++)
{
sum += scores[i];
}
Console.WriteLine("{0}的总成绩是{1}",name,sum);
}
}
方法递归:方法调用自己
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
TellStory();
Console.ReadKey();
}
static void TellStory()
{
Console.WriteLine("从前有座山,山上有座庙,庙里老和尚给小和尚讲故事,");
TellStory();
}
}
}
类和结构
类和结构是不同的,类是面向对象的,面向对象的三个特征封装、继承、多态,结构一个也没有,结构是面向过程(强调干什么)。(类和结构都可拥有结构、字段、属性和方法)
构造函数构造函数是特殊的方法,在实例化对象的时候被执行
this关键字1)代表当前类的对象
2)在类中显式的调用本类的构造函数
public class Student
{
public string Name { get; set; }//自动属性
public int Age { get; set; }
public string Address { get; set; }
public Student(string name,int age,string address)
{
Name = name;
Age = age;
Address = address;
}
public Student(string name,int age):this(name,age,"河南")
{
//this表示调用本类的构造函数
}
}
值类型和引用类型
值类型:int、double、decimal、char、bool、enum、struct
引用类型:类、数组、接口
栈、堆、静态存储区域
不需要额外的代码,计算机自动完成转换,适用于取值范围小的数据类型转换为取值范围大的数据类型。
显示转换又称强制转换,需要编写代码才能完成转换,适用于取值范围大的数据类型转换为取值范围小的数据类型。
封装我们可能会在一些类中,写一些重复的成员,我们可以将这些重复的成员单独封装到一个类中,作为这些类的父类。
继承1)继承的单根性:一个子类只能有一个父类
2)继承的传递性:
子类没有继承父类的构造函数(因为子类无法调用父类的构造函数)。实例化子类时子类会默认调用父类的无参的构造函数先实例化父类,父类实例化之后子类才能使用父类中的成员。如果在父类中重新写了一个有参的构造函数后,那个无参的就被干掉了,实例化子类时子类就掉用不到了,此时就会报错。
解决方法:
1)在父类中重新写一个无参的构造方法;
2)在子类显式的调用父类的构造方法,使用关键字base()
1)创建对象
2)隐藏从父类那里继承过来的同名成员
1)子类可以赋值给父类:如果有一个地方需要一个父类作为参数我们可以给一个子类参数代替
2)如果父类对象中装的是子类对象,我们可以将这个父类对象强转成子类对象
子类对象可以调用父类的成员,但是父类对象永远只能调用自己的成员
接口、抽象类等父类对象(包括里面有虚方法的类)可以调用实现接口、抽象类的子类中的实现成员(包括在子类中重写虚方法的成员)及其父类本身的成员,子类中的其他成员不允许调用。
is :is表示类型转换 如果可以转换返回true,不可以转换返回一个false
as :as表示类型转换 如果转换成功则返回对应的对象,转换失败返回null
装箱和拆箱 枚举和结构是值类型,枚举和结构本质上是变量 编码编码:就是数据以什么方式的二进制存储在计算机中。
编码方式:美国ASCII 、中国 GBK等, 各国都有自己的编码方式
乱码的原因:将数据转化成二进制进行保存时使用的编码方式和将二进制转化成数据使用的编码方式不同。
类和结构的区别(总结的有问题,需要重新总结)类:引用类型 值分配在内存的堆中
结构:值类型 值分配在内存的栈中
相同点:类和结构中,如果字段前不加访问修饰符,默认为private
不同点:在类的构造函数中,你既可以给字段赋值、也可以给属性赋值,而在结构的构造函数中你只能给字段赋值;在类中构造函数可以有重载,而在结构中构造函数不可以有重载(就是在结构中的构造函数必须给所有的字段赋值,在类中的构造函数可以不用给所有的字段赋值);类中的new是在堆中开辟空间创建对象和执行构造函数,结构中的new只是执行构造函数;在类中如果写了一个构造函数(不管是有参的还是无参)默认的无参构造函数就会被干掉,在结构中如果写了一个有参数的构造函数,无参的构造函数不会被干掉,也就是在结构中最多只能有两个构造函数一个是无参的,一个是全参的,类中的构造函数可以有很多个因为他可以重载。
类的成员“纵向扩展”(行为改变,版本增高)
重写与隐藏发生的条件:
函数成员(方法,属性,事件等)可见、签名一致。
public class Vehical
{
public virtual void Run()
{
Console.WriteLine("Vehical is running");
}
}
public class Car: Vehical
{
public override void Run()
{
Console.WriteLine("Car is running");
}
}
public class RaceCar1:Car
{
public new void Run() //new隐藏了Car.Run()
{
Console.WriteLine("RaceCar is running");
}
}
public class RaceCar2 : Car
{
public override void Run() //override重写了Car.Run()
{
Console.WriteLine("RaceCar is running");
}
}
public class RaceCar3 : Car
{
}
class Program
{
static void Main(string[] args)
{
Vehical v = new RaceCar1();
v.Run(); //Car is running
((Car)v).Run(); //Car is running
((RaceCar1)v).Run(); //RaceCar is running
v = new RaceCar2();
v.Run(); //RaceCar is running
((Car)v).Run(); //RaceCar is running
((RaceCar2)v).Run(); //RaceCar is running
v = new RaceCar3();
v.Run(); //Car is running
((Car)v).Run(); //Car is running
((RaceCar2)v).Run(); //Car is running
Console.ReadKey();
}
}
多态
基于重写机制(virtual/abstract->override)
父类对象引用子类对象,父类行为由最新版本决定(如上图)
抽象修饰符abstract可以与类,方法,属性,事件和索引器一起使用。
为基类而生的抽象类与开闭原则一般类->抽象类->接口:越来越抽象,内部实现的东西越来越少
抽象类是未完全实现逻辑的类,内部有未实现的方法、属性、事件或索引器。
封装确定的,开放不确定的,下推到合适的子类中去实现
抽象类为复用而生:专门作为基类来使用,也具有解耦的功能
接口是完全未实现逻辑的类,是“纯虚类”。接口里的成员可以是方法、属性、事件或索引器。成员全部为public
接口为解耦而生,“高内聚,低耦合”,方便单元测试。
接口和抽象类都不能实例化,只能用来声明变量,引用具体类的实例。
抽象类:一个类里面一旦有了 abstract 成员,类就变成了抽象类,就必须标 abstract。
abstract 成员即暂未实现的成员,因为它必须在子类中被实现,所以抽象成员不能是 private 的。
因为抽象类内部还有未实现的成员,计算机不知道怎么调用这类成员,于是编译器干脆不允许你实例化抽象类。
一个类不允许实例化,它就只剩两个用处了:
a.作为基类,在派生类里面实现基类中的 abstract 成员
b.声明基类(抽象类)类型变量去引用子类(已实现基类中的 abstract 成员)类型的实例,这又称为多态
virtual(虚方法)还是有方法体的,只不过是等着被子类重写 ;abstract(纯虚方法)却连方法体都没有。
开闭原则:我们应该封装那些不变的、稳定的成员,而把那些不确定的,有可能改变的成员声明为抽象成员,并且留给子类去实现。
1. 因为 interface 要求其内部所有成员都是 public 的,所以就把 public 去掉了
2. 接口本身就包含了“是纯抽象类”的含义(所有成员一定是抽象的),所以 abstract 也去掉了
3. 因为 abstract 关键字去掉了,所以实现过程中的 override 关键字也去掉了
4.实现接口和显式实现所有接口的区别
显式实现接口只能通过接口来调用实现的方法,不能通过具体类;实现接口既可以是有接口也可以使用具体类.
---------------------------------------------- 华丽的分割线 ------------------------------------------------------------
索引器(Indexer)
索引器允许一个对象可以像数组一样通过下标来进行访问。
当您为一个类创建了索引器时,该类的行为就像是一个虚拟数组一样。您可以通过数组访问运算符[]来访问该类的成员。
索引器的行为的声明类似于属性,您可以使用get和set访问器来定义索引器,区别在于定义索引器的时候不带名称,但带有this关键字,它指向对象实例。
创建索引器示例代码如下:
public class IndexName
{
private string[] nameList = new string[10];
public string this[int n] //创建索引器
{
get
{
string temp;
if (n >= 0 && n < 10)
{
temp = nameList[n];
}
else
{
temp = "";
}
return temp;
}
set
{
if (n >= 0 && n < 10)
{
nameList[n]=value;//
}
}
}
}
static void Main(string[] args)
{
IndexName indexName = new IndexName();
for(int i = 0; i < 10; i++)
{
indexName[i] = "DefaultName";
}
for (int i = 0; i < 10; i++)
{
Console.WriteLine(indexName[i]);
}
}
重载索引器
public class IndexName
{
private string[] nameList = new string[10];
public string this[int index] //创建索引器
{
get
{
string temp;
if (index >= 0 && index < 10)
{
temp = nameList[index];
}
else
{
temp = "";
}
return temp;
}
set
{
if (index >= 0 && index < 10)
{
nameList[index] = value;//
}
}
}
public int this[string name]//重载索引器
{
get
{
int index = 0;
while (index < 10)
{
if (nameList[index] == name)
return index;
index++;
}
return index;
}
}
}
static void Main(string[] args)
{
IndexName indexName = new IndexName();
for (int i = 0; i < 10; i++)
{
indexName[i] = "DefaultName";
}
for (int i = 0; i < 10; i++)
{
Console.WriteLine(indexName[i]);
}
int index = indexName["XiaoMing"];
}
泛型
委托-Lambda-Linq-事件
(里面还有一个表达式目录树没有完成,目前阶段就不总结了)
反射 特性 迭代器 操作符重载 扩展方法 匿名类型 属性 隐式类型 nameof表达式nameof 表达式生成变量、类型或成员的名称作为字符串常量:
Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic Console.WriteLine(nameof(List)); // output: List //这里稍微注意一下 Console.WriteLine(nameof(List .Count)); // output: Count Console.WriteLine(nameof(List .Add)); // output: Add var numbers = new List { 1, 2, 3 }; Console.WriteLine(nameof(numbers)); // output: numbers Console.WriteLine(nameof(numbers.Count)); // output: Count Console.WriteLine(nameof(numbers.Add)); // output: Add
如前面的示例所示,在类型和命名空间的情况下,生成的名称不是完全限定的。
在逐字标识符的情况下,@ 字符不是名称的一部分,如以下示例所示:
var @new = 5; Console.WriteLine(nameof(@new)); // output: new
nameof 表达式在编译时计算,在运行时无效。
您可以使用 nameof 表达式使参数检查代码更易于维护:
public string Name
{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null");
}
C# 6 及更高版本中提供了 nameof 表达式。
字符串插值$ 特殊字符将字符串文字标识为插值字符串。 插值字符串是可能包含插值表达式的字符串文字。 当一个插值字符串被解析为一个结果字符串时,带有插值表达式的项目被替换为表达式结果的字符串表示。 此功能从 C# 6 开始可用。
字符串插值提供了一种更易读、更方便的语法来格式化字符串。 它比字符串复合格式更容易阅读。 比较以下使用这两种功能产生相同输出的示例:
string name = "Mark";
var date = DateTime.Now;
// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.



