泛型 :
允许我们延迟编写数据类型, 直到真正使用时确定类型的一种规范
1.可以创建自己的泛型接口、泛型类、泛型方法、泛型集合 ,泛型事件和泛型委托
2.泛型的格式: 结构<泛型的名称T> 在定义泛型时T通常用作变量类型名称。但实际上 T 可以用任何有效名称代替
3.泛型方法在定义是提供泛型的类型 在调用方法时 提供具体的延迟编写的类型
4.泛型无法直接使用运算符 比如 + - < > 等 但是能使用Object中的属性和方法
泛型方法
public static void Test<T>(T a, T b)
{
string temp = a.ToString();
string temp1 = b.ToString();
Console.WriteLine(temp1+temp);
}
public static void Test1<T,鸡>(T a, T b,鸡 c)
{
string temp = a.ToString();
string temp1 = b.ToString();
Console.WriteLine(c.GetType().ToString());
Console.WriteLine(temp1 + temp);
}
泛型类
C# 语言中泛型类的定义与泛型方法类似,是在泛型类的名称后面加上<T>,当然,也可以定
义多个类型,即“<T1,T2,・・・>”。
class People<T, C> {
private T _age;
public T Age { get; set; }
public T Eat(C a ,C b) {
return _age;
}
}
泛型接口
interface Inter<T>
{
void show(T t);
}
//定义接口Inter的子类InterImpA,明确泛型类型为String (2)
public class InterImpA : Inter<int>
{
//子类InterImpA重写方法show,指明参数类型为string
public void show(int t)
{
Console.WriteLine(t);
}
}
//定义接口Inter的子类InterImpB,直接声明 (1)
public class InterImpB<T> : Inter<T>
{
public void show(T t)
{
Console.WriteLine(t);
}
}
c#内置了 IComparable 接口用于在要比较的对象的类中实现,可以比较任意两个对象。
public class Student : IComparable<Student> {
int Age { get; set; }
//定义比较方法,按照学生的年龄比较
public int CompareTo(Student other) {
if (this.Age == other.Age)
{
return 1;
}
else if (this.Age < other.Age)
{
return 2;
}
else {
return 3;
}
}
}
2.泛型可以提升代码性能,避免了装箱和拆箱的内存操作
泛型和Object类型的区别
值类型转换成引用类型。这个转换称为装箱。相反的过程称为拆箱
int number = 10;
// 装箱
object obj = number;
// 拆箱
number = (int) obj;
C# 中 Object 是一切类型的基类,可以用来表示所有类型
Object 类型
> 优点:
> 1. object类型可以用来引用任何类型的实例;
> 2. object类型可以存储任何类型的值;
> 3. 可以定义object类型的参数;
> 4. 可以把object作为返回类型。
> 缺点:
> 1. 会因为程序员没有记住使用的类型而出错,造成类型不兼容;
> 2. 值类型和引用类型的互化即装箱拆箱使系统性能下降
装箱(从值类型转换到引用类型)需要经历如下几个步骤:
int a =10
object ob = a;
首先在堆上分配内存。这些内存主要用于存储值类型的数据。
接着发生一次内存拷贝动作,将当前存储位置的值类型数据拷贝到堆上分配好的位置。
最后返回对堆上的新存储位置的引用。
拆箱(从引用类型转换为值类型)的步骤则相反:
// 拆箱
number = (int) obj;
首先检查已装箱的值的类型兼容目标类型。
接着发生一次内存拷贝动作,将堆中存储的值拷贝到栈上的值类型实例中。
最后返回这个新的值。
频繁拆装箱导致性能问题
由于拆箱和装箱都会涉及到一次内存拷贝动作,因此频繁地进行拆装箱会大幅影响性能
泛型和装箱拆箱 对比
public void abTest(object a, object b) {
Console.WriteLine(a.ToString());
Console.WriteLine(b.ToString());
}
public void abTest(T a, T b)
{
Console.WriteLine(a.ToString());
Console.WriteLine(b.ToString());
}
3. 泛型中的数据约束可以指定泛型类型的范围 - 泛型约束
泛型约束总共有五种。
约束 说明
T:结构 类型参数必须是值类型
T:类 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
T:new() 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。
T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。
where T : struct //约束 T 必须是值类型
where K : class //约束 K 必须是引用类型
where V : IFace<T> //约束 V 必须实现 IFace 接口
where W : K //约束 W 必须是 K 类型,或者是 K 类型的子类
where X : class, new() //约束 X 必须是引用类型,并且有一个无参数的构造函数,当有多个约束时,new()必须写在最后
where Y : MyClass2 //约束 Y 必须是 MyClass2 类型,或者继承于 MyClass2 类
//例子:
internal class Program
{
static void Main(string[] args)
{
Person<int,string,IFaceClass,string> zhangsan = new Person<int,string,IFaceClass,string>();
zhangsan.Age = 10;
Person<float,Program,IFaceClass,Program> zhangsan1 = new Person<float,Program,IFaceClass,Program>();
zhangsan1.Age = 10.0f;
Person<double, People, IFaceClass, Man> zhangsan2 = new Person<double, People, IFaceClass, Man>();
zhangsan1.Age = 10.0f;
}
}
public class People:IComparable<People>{
public int CompareTo(People other) {
return 1;
}
}
public class Man : People {
}
public interface IFace<T> {
void IFaceMothod();
}
public class IFaceClass:IFace<int> ,IFace<float>,IFace<double>{
public void IFaceMothod() {
}
}
public class Person <T,K,V,W>
where T : struct
where K : class
where V : IFace<T>
where W : K
{
private T _age;
public T Age { get { return _age; } set { _age = value; } }
public K TesT { get; set; }
}
委托约束
枚举约束 后续再说
4.泛型成员因类型不确定,可能是类、结构体、字符、枚举……所以不能使用算术运算符、比较运算符等进行运算!
注意,可以使用赋值运算符。
5.泛型默认值问题 需要用 default()方法
之所以会用到default关键字,是因为需要在不知道类型参数为值类型还是引用类型的情况下,为对象实例赋初值。
引用类型会返回 null,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或 null 的每个结构成员,具体取决于这些结构是值类型还是引用类型。对于可以为 null 的值类型,默认返回 System.Nullable<T>
///如果我们用int型来绑定泛型参数,那么T就是int型,
///那么注释的那一行就变成了 int t = null;显然这是无意义的。
///为了解决这一问题,引入了default关键字:
TestDefault<int?> testDefault = new TestDefault<int?>();
Console.WriteLine(testDefault.foo());
class TestDefault<T>
{
public T foo()
{
// T t = null; //错误赋值方式
T t = default(T);
return t;
}
}
List 泛型集合
List 与其他数组的比较:
List与静态数组(Array类)比较类似,都用于存放一组相同类型的值。
List与静态数组(Array类)区别 元素长度不固定 。
//创建字符串类型List的对象listStr
List<string> listStr = new List<string>();
//创建元素位置且添加元素
listStr.Add("a");
Console.WriteLine(listStr[0]);
//修改对应位置的元素
listStr[0] = "b";
for (int i = 1; i <=10; i++)
{
listStr.Add(i.ToString());
}
//添加元素
listStr.AddRange(listStr);
foreach (var item in listStr)
{
Console.WriteLine(item);
}
listStr.Insert(0, "a");
listStr.IndexOf("a");
listStr.Remove("a");
listStr.RemoveAt(0);
listStr.RemoveAll(x => x == "a");
listStr.FindAll(x => x == "a");
ArrayList与List<T>
ArrayList
ArrayList的优点:
ArrayList大小是按照其中存储的数据来动态扩充与收缩的 长度不固定
ArrayList可以很方便地进行数据的添加插入删除
ArrayList 可以存储任意类型
using System.Collections;
ArrayList aList = new ArrayList();
//插入
aList.Add(123);
aList.Add("ABC");
aList.Insert(1, 123 + "ABC"); //123 + "ABC" ->(隐式转换)"123ABC"
//移除
aList.RemoveAt(0); //索引处
aList.Remove("ABC");
ArrayList的缺点:
ArrayList在存储数据时使用object类型进行存储的
ArrayList不是类型安全的,使用时很可能出现类型不匹配的错误
就算都插入了同一类型的数据,使用时我们也需要将它们转化为对应的原类型来处理
ArrayList 存储在装箱和拆箱操作,导致其性能低下
using System;
// 引用 ArrayList类所在的命名空间
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ZN_LessonDay19_1_ArrayList
{
internal class Program
{
static void Main(string[] args)
{
// 创建ArrayList对象
ArrayList arrayList = new ArrayList();
// 添加元素
arrayList.Add("张三");
arrayList.Add(1);
arrayList.Add(11.0f);
arrayList.Add(true);
arrayList.Add(new Person());
arrayList.Add(new object());
arrayList.Add(new int[5]);
arrayList.Add(new List<string>());
arrayList.Add(arrayList);
//添加多个元素
arrayList.AddRange(arrayList);
//添加不可变数组
arrayList.AddRange(new string[] { "张三", "张三", "张三" });
arrayList.AddRange(new List<string>() {"1","2"});
arrayList.AddRange(Array.CreateInstance(typeof(int), 5));
//总结 : 接口作为参数类型时,可以传入实现了该接口的任意对象
//获取集合的长度
Console.WriteLine(arrayList.Count);
//读取集合中的元素 根据索引值 读取集合中的元素
//元素类型都为 object
object ob1 = arrayList[1];
//值类型拆箱
int num = (int)ob1;
//引用类型 类型转换
object ob4 = arrayList[4];
Person p1 = (Person)ob4;
p1.Name = "张三";
//Object类型 转换成子类时
//在编译阶段不会报错 但是如果类型不一致 在运行阶段就会报错
//object ob8 = arrayList[8];
//Person p2 = (Person)ob8;
//p2.Name = "张三";
//修改数组元素
arrayList[8] = new Person();
object ob8 = arrayList[8];
Person p2 = (Person)ob8;
p2.Name = "张三";
Console.WriteLine($"删除前的长度{arrayList.Count}");
//删除数组元素
//根据索引值删除元素
arrayList.RemoveAt(8);
//根据对象删除元素
arrayList.Remove(p2);
Console.WriteLine($"删除后的长度{arrayList.Count}");
//遍历数组元素
//for (int i = 0; i < arrayList.Count; i++)
//{
// Console.WriteLine(arrayList[i]);
//}
foreach (object item in arrayList)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
public class Person
{
public string Name { get; set;}
}
}
C#中的Dictionary字典类
特点:
必须包含名空间System.Collection.Generic
Dictionary里面的每一个元素都是一个键值对(由二个元素组成:键和值)
键必须是唯一的,而值不需要唯一的
字典 长度是不固定的 随着元素增减 而改变
键和值都可以是任何类型(比如:string, int, 自定义类型,等等)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp33
{
internal class Program
{
static void Main(string[] args)
{
//字典的对象
Dictionary<string,int> dic = new Dictionary<string,int>();
Dictionary<int,string> dic1 = new Dictionary<int,string>();
//添加元素
dic1.Add(0, "a");
dic1.Add(1, "b");
dic1.Add(2, "c");
dic.Add("1", 123);
dic.Add("2", 333);
dic.Add("abc", 333);
dic.Add("3", 456);
//查找
Console.WriteLine(dic1[0]);
Console.WriteLine(dic["3"]);
//修改
dic1[2] ="d";
dic["2"] = 444;
for (int i = 0; i < dic1.Count; i++)
{
Console.WriteLine(dic1[i]);
}
//dic.Keys 集合
foreach (string item in dic.Keys)
{
Console.WriteLine(dic[item]);
}
// dic.Values 集合
foreach (int item in dic.Values)
{
Console.WriteLine(item);
}
//
foreach (KeyValuePair<string,int> item in dic)
{
Console.WriteLine(item.Key);
Console.WriteLine(item.Value);
}
////移除元素
bool isRmove = dic.Remove("aaadsasdasd");
if (isRmove)
{
Console.WriteLine("删除成功");
}
else {
Console.WriteLine("删除失败");
}
// //判断字典中是否包含某个key
bool isTrue = dic.ContainsKey("2");
if (isTrue)
{
dic.Remove("2");
}
else {
dic.Add("2",11);
}
// //清空
dic.Clear();
// //字典中key/value的个数
Console.WriteLine(dic.Count);
Console.ReadKey();
}
}
}
常用属性
名称 说明
Comparer 获取用于确定字典中的键是否相等的 IEqualityComparer<T>。
Count 获取包含在 Dictionary<TKey, TValue> 中的键/值对的数目。
Item 获取或设置与指定的键相关联的值。
Keys 获取包含 Dictionary<TKey, TValue> 中的键的集合。
Values 获取包含 Dictionary<TKey, TValue> 中的值的集合。
常用方法
名称 说明
Add 将指定的键和值添加到字典中。
Clear 从 Dictionary<TKey, TValue> 中移除所有的键和值。
ContainsKey 确定 Dictionary<TKey, TValue> 是否包含指定的键。
ContainsValue 确定 Dictionary<TKey, TValue> 是否包含特定值。
Remove 从 Dictionary<TKey, TValue> 中移除所指定的键的值。
ToString 返回表示当前对象的字符串。 (继承自 Object。)
C# 结构体(Struct)
1.结构体是值类型数据结构。 内存中 值类型存在栈中 引用类型存在堆中 (字符串字面量创建在栈中)
2.引用类型派生自 System.Object ,而值类型均隐式派生自 System.ValueType
// 数值类型
//is 代表 判断左边内容 和右边的类型 是否一致 如果一致 返回true 反之为false
Console.WriteLine(1 is ValueType); // true
// 布尔类型
Console.WriteLine(true is ValueType); // true
// 结构类型
Console.WriteLine(new Person() is ValueType); // false
// 枚举类型
Console.WriteLine(EnumTest.Item1 is ValueType); // true
3.struct 关键字用于创建结构体
struct MyStruct2 {
}
3.结构可带有方法、字段、属性、运算符,委托和事件。
struct MyStruct2 {
private int A;
public int Age { get; set; }
public void Test() { };
delegate void Delegate1();
event Delegate1 Delegate2;
public static MyStruct2 operator -(MyStruct2 stu1, MyStruct2 stu2)
{
return new MyStruct2();
}
}
4.结构体可定义构造函数,但不能定义析构函数。不能定义无参构造函数。无参构造函数(默认)自带
如果定义有参构造函数 必须在 构造函数内部 初始化所有 字段和属性
struct MyStruct2 {
private int A;
public int Age { get; set; }
public void Test() { };
public delegate void Delegate1();
public event Delegate1 Delegate2;
public MyStruct2(int a,int age,Delegate1 delegate1) {
A = a;
Age = age;
Delegate2 = delegate1;
}
//~MyStruct() { //错误
//}
}
5.结构体可以继承一个或多个接口 但是不能继承其他类
6.结构不支持被其他类继承。
7.结构成员不能指定为virtual 或 protected、
8.结构可以使用 New 创建对象 也可以不使用 ,如果结构中有属性 必须使用new 创建对象
9.结构可以嵌套在类中使用 也可以和类并列定义在命名空间中
类和结构总结:
1. 类和结构实际上都是创建对象的模板, 每个对象都包含数据,并提供了处理和访问数据的方法
2. 类是引用类型 对象存于堆中 可以通过GC管理内存 结构是值类型 对象存于栈中 、
3 结构不能被继承 也不能继承其他类 但是能继承接口
4. 结构和类 都能使用new 创建对象 但是结构也可以不使用
5. 结构作为方法参数 默认是值传递 类类型默认是引用传递
枚举
作用:
枚举是描述一组整数值的结构 使数字更具有具体意义
1.枚举是值类型
2.枚举类型使用 enum 关键字声明的。
3.枚举是一组整型常量 默认是从 0开始 也可以自己定义范围
4.枚举使用enum关键字来声明,与类同级。枚举本身可以有修饰符,但枚举的成员始终是公共的,不能有访问修饰符。枚举本身的修饰符仅能使用public和internal。
5.枚举都是隐式密封的,不允许作为基类派生子类
public enum MyEnum {
张三,
王五,
李四,
赵六,
孙七=77,
吴八,
钱九=1000,
温十
}
Console.WriteLine("请输入数字:");
int a1 = Convert.ToInt32(Console.ReadLine());
MyEnum myEnum = new MyEnum();
if (a1 == 0)
{
myEnum = MyEnum.张三;
}
else if (a1 == 1)
{
myEnum = MyEnum.王五;
}switch(myEnum)
{
case MyEnum.张三:
Console.WriteLine("张三");
break;
case MyEnum.王五:
Console.WriteLine("王五");
break;
default:
Console.WriteLine("默认");
break;
}
委托和事件
委托
1.委托就是把方法(函数)变成一种引用类型的方式
2.委托 关键字 delegate(代理)
3.委托分为定义委托和使用委托
定义委托
1.定义委托和定义类一样可在命名空间中定义,也可以像变量一样在类的内部定义,
3.委托可以使用修饰符:public,private,protected等。
3.委托可以根据不同类型的方法(有参无参 有返回值 无返回值等)定义 多个委托类型
delegate void IntMethod(int x);
//定义了一个 无返回值有一个int类型参数的 委托
使用委托
//1. 创建委托类型的对象 用来管理某个符合类型的方法 并且使用委托对象 来调用方法
delegate void VoidDelegate();
delegate void VoidDelegate_1(int a);
delegate void VoidDelegate_2(int a ,int b);
internal class Program
{
static void Main(string[] args)
{
VoidDelegate voidDelegate = new VoidDelegate(ProgramMothod);\
voidDelegate();
VoidDelegate_1 voidDelegate_1 = new VoidDelegate_1(ProgramMothod_1);
voidDelegate_1(10);
VoidDelegate_2 voidDelegate_2 = new VoidDelegate_2(ProgramMothod_2);
voidDelegate_2(10, 20);
Console.ReadKey();
}
public static void ProgramMothod() {
Console.WriteLine("1");
}
public static void ProgramMothod_1(int a) {
Console.WriteLine(a);
}
public static void ProgramMothod_2(int a, int b) {
Console.WriteLine(a+b);
}
}
使用泛型委托
delegate void VoidDelegate_3<T>(T a, T b);
internal class Program
{
static void Main(string[] args)
{
VoidDelegate_3<string> voidDelegate_3 = new VoidDelegate_3<string> (ProgramMothod_3);
VoidDelegate_3<int> voidDelegate_4 = new VoidDelegate_3<int>(ProgramMothod_2);
Console.ReadKey();
}
public static void ProgramMothod_2(int a, int b) {
Console.WriteLine(a+b);
}
public static void ProgramMothod_3(string a, string b)
{
Console.WriteLine(a + b);
}
}
使用委托作为方法参数
static void Main(string[] args)
{
ProgramMothod_3(3, 4, ProgramMothod_2);
Console.ReadKey();
}
public static int ProgramMothod_2(int a, int b)
{
return a + b;
}
public static void ProgramMothod_3(int w,int y,VoidDelegate_4 voidDelegate_4)
{
int temp = voidDelegate_4(w,y);
Console.WriteLine(temp);
}
}
使用 Action和Func委托
方法的返回类型和名字千千万万,无法对每个方法都去定义对应的委托,.net为了方便使用委托,定义了两个泛型委托。
Action
Action委托表示一个void返回类型的方法
Func
Func委托表示一个带返回类型的方法
至多可以传递16种不同类型的参数和一个返回类型。返回类型放在<>最后一个。
static void Main(string[] args)
{
Action action1 = new Action(ProgramMothod_1);
Action<int, int> action = new Action<int, int>(ProgramMothod_2);
Func<int> func = new Func<int>(ProgramMothod_4);
Func<int,int,int> func1 =new Func<int, int, int> (ProgramMothod_5);
Console.ReadKey();
}
public static void ProgramMothod_1() {
}
public static void ProgramMothod_2(int a, int b) {
Console.WriteLine(a+b);
}
public static int ProgramMothod_4()
{
return 1;
}
public static int ProgramMothod_5(int a ,int b)
{
return a+b;
}
}
委托的多播
1.委托对象可使用 "+" 运算符进行合并。
2."-" 运算符可用于从合并的委托中移除组件委托
3.只有相同类型的委托可被合并
internal class Program { static void Main(string[] args) { Action totalAction; Action action1 = new Action(ProgramMothod_1); Action action2 = new Action(ProgramMothod_2); totalAction = action1; totalAction += action2; totalAction(); Console.ReadKey(); } public static void ProgramMothod_1() { } public static void ProgramMothod_2() { }
事件
1.事件基于委托的,可以为任何一种委托提供一种发布\订阅机制。(类似委托多播)
2.使用event关键字将一个委托类型定义为事件 事件就是委托的一个对象
internal class Program
{
static void Main(string[] args)
{
Heater heater = new Heater();
heater.BoilEvent += Heater1;
heater.BoilEvent += Heater2; //添加事件
heater.BoilWater();//执行事件
}
public static void Heater1(int x) {
Console.WriteLine("水已经{0}度了,可以用茶杯接水了",x);
}
public static void Heater2(int x)
{
Console.WriteLine("水已经{0}度了,可以用大桶接水了", x);
}
}
//烧开水事件 类
public class Heater
{
private int temperature;//水温
public delegate void BoilHandle(int x);//声明关于事件的委托
public event BoilHandle BoilEvent;//声明水要烧开的事件
public void BoilWater()
{ //烧水的方法
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 96)
{
if (BoilEvent != null)
{
BoilEvent(temperature);
}
}
}
}
}
普通匿名函数
1.针对委托的使用
2.可以快捷的使委托实例化。
3.不建议再使用普通匿名函数,C#3.0后使用lambda表达式替代匿名函数
4. 什么情况使用匿名函数 (1). 方法本身不自己调用 (2).方法要绑定委托时
(3.) 方法内的功能逻辑不能太复杂
匿名函数基本写法:
匿名函数的写法 : class Program { delegate int MyDelegate(int a, int b); static void Main(string[] args) { MyDelegate md = delegate(int a, int b) { return a + b; }; int sum = md(1,2); Console.WriteLine(sum); } }
事件与普通匿名函数
nternal class Program
{
static void Main(string[] args)
{
Heater heater = new Heater();
heater.BoilEvent += delegate (int x) //添加匿名函数
{
Console.WriteLine("水已经{0}度了,可以用茶杯接水了", x);
};
heater.BoilEvent += delegate (int x) //添加匿名函数
{
Console.WriteLine("水已经{0}度了,可以用大桶接水了", x);
};
heater.BoilWater();//执行事件
}
}
public class Heater
{
private int temperature;//水温
public delegate void BoilHandle(int x);//声明关于事件的委托
public event BoilHandle BoilEvent;//声明水要烧开的事件
public void BoilWater()
{ //烧水的方法
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 96)
{
if (BoilEvent != null)
{
BoilEvent(temperature);
}
}
}
}
}
lambda表达式
1.普通匿名函数的升级版本 (箭头函数)
2.比普通匿名函数更为简洁,数据类型可以不用写
3.匿名函数无法取消事件绑定 如果要取消 使用普通函数
class Program
{
delegate int MyDelegate(int a, int b);
static void Main(string[] args)
{
MyDelegate md = (a,b)=>{return a+b;};
int sum = md(1, 2);
Console.WriteLine(sum);
}
}
事件与lambda表达式
internal class Program
{
static void Main(string[] args)
{
Heater heater = new Heater();
heater.BoilEvent += x =>
{
Console.WriteLine("水已经{0}度了,可以用茶杯接水了", x);
};
heater.BoilEvent += x=> //添加匿名函数
{
Console.WriteLine("水已经{0}度了,可以用大桶接水了", x);
};
heater.BoilWater();//执行事件
Console.ReadKey();
}
}
public class Heater
{
private int temperature;//水温
public delegate void BoilHandle(int x);//声明关于事件的委托
public event BoilHandle BoilEvent;//声明水要烧开的事件
public void BoilWater()
{ //烧水的方法
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 96)
{
if (BoilEvent != null)
{
BoilEvent(temperature);
}
}
}
}
}
运算符重载
1.含义
是对已有的运算符重新定义新的运算规则,以适应不同的数据类型
特点:
0.运算符本质是方法
1.运算符重载的声明方式:operator 关键字
2.必须用public修饰且必须是类的静态的方法
3.重载运算符的方法 不用主动调用 只要使用重载的运算符就相当调用了方法
4.运算符只能采用值参数,不能采用ref或out参数
5.重载运算符的返回值不一定必须是自己,但一定不能是void
运算符 可重载性
! ++、--、 可以重载这些一元运算符
+、-、*、/、%、&、| 可以重载这些二元运算符
==、!=、<、>、<=、>= 可以重载比较运算符,必须成对重载
&&、|| 不能重载条件逻辑运算符,但可以使用能够重载的&和|进行计算
+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= 不能显式重载赋值运算符,在重写单个运算符如+、-、%时,它们会被 隐式重写
=、.、?:、->、new、is、sizeof、typeof () [] 不能重载这些运算符
+ - * / % ++ -- !
public static Student operator +(Student stu1,Student stu2)
{
return new Student(stu1.Age + stu2.Age, stu1.Name + stu2.Name);
}
public static Student operator -(Student stu1, Student stu2)
{
return new Student(stu1.Age - stu2.Age, stu1.Name + stu2.Name);
}
public static Student operator ++(Student stu1)
{
stu1.Age++;
stu1.Height++;
return stu1;
}
public static bool operator !(Student stu1)
{
if (stu1.Age < 20)
{
return !false;
}
else {
return !true;
}
}
public static Student operator &(Student stu1,Student stu2)
{
if (stu1.Age < 20 && stu2.Age<10)
{
return new Student(stu1.Age + stu2.Age);
}
else
{
return new Student(stu1.Age - stu2.Age);
}
}
C# 异常处理
含义:异常是在程序执行期间出现的问题。例如除零错误、空引用访问、文件不存在等
C# 异常处理四个关键字:try、catch、finally 和 throw。
- try:一个 try 块包括一块要被检测异常的代码块
- catch:表示对异常的捕获和处理。
- finally:不管异常是否存在都会执行。
- throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。
格式:
try
{
// 引起异常的语句
}
catch( ExceptionName e1 )
{
// 错误处理代码
throw e1;
}
finally
{
// 要执行的语句
}
C# 中的异常
在C#中,异常分为两种类型:系统异常和自定义异常。
C# 中的系统异常类主要继承与 System.Exception 类
常见的系统异常类:
ArithmeticException:算术异常,如除以零。
NullReferenceException:空引用异常,当试图在引用为null的对象上调用方法或访问属性时引发。
IndexOutOfRangeException:索引越界异常,当数组或集合的索引超出范围时引发。
系统异常使用
//0做为被除数
int result = 0;
try
{
int num1 = 10;
int num2 = 0;
result = num1 / num2;
}
catch (SystemException e)
{
Console.WriteLine("异常错误为: {0}", e);
//throw e;
}
finally
{
Console.WriteLine(result);
}
////数组越界
//int[]intArray =new int[3];
//try
//{
// intArray[4] = 10;
//}
//catch (SystemException e)
//{
// Console.WriteLine("异常错误为111: {0}", e);
// // throw e;
//}
//finally
//{
// Console.WriteLine("1");
//}
多个 catch 语句捕获不同类型的异常,以防 try 块在不同的情况下生成多个异常
try
{
//using语句可用于确保在使用完资源后释放它,以避免资源泄漏 等同于reader.Dispose() 释放资源
using (StreamReader reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
}
}
catch (FileNotFoundException ex)
{
Console.WriteLine("文件不存在:" + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("IO错误:" + ex.Message);
}
finally
{
Console.WriteLine("处理结束。");
}
自定义异常处理
//对于People 对象年龄限制的异常处理
static void Main(string[] args)
{
Peopele peopele = new Peopele();
try
{
peopele.Age = 140;
}
catch (SystemException e)
{
Console.WriteLine("TempIsZeroException: {0}", e.Message);
}
finally {
Console.WriteLine("年龄:{0}符合要求",peopele.Age);
}
Console.ReadKey();
}
}
//对于People 对象年龄限制的异常处理
public class Peopele
{
private int _age;
public int Age {
get { return _age; }
set {
if (value < 0 || value > 100)
{
throw (new SystemException("年龄不符合要求"));
}
else {
_age = value;
}
} }
}
值元组
值元组(ValueTuple) 是轻量级、值类型的元组类型,用于简洁地存储多个不同类型的值
//创建方式1
var Tuple = (1, "张三");
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
//创建方式2
(int Id, string Name) Tuple = (1, "张三");
//创建方式3
var tuple = (Id: 1, Name: "张三");
Console.WriteLine(tuple.Id);
Console.WriteLine(tuple.Name);
解构值元组
将元组的元素拆分到多个独立变量中
var tuple = (Id: 1, Name: "张三");
//定义变量
int Id;
string Name;
(Id, Name) = tuple;
Console.WriteLine(tuple.Id);
Console.WriteLine(tuple.Name);
// 忽略某个元素(用_)
var (_, Name1) = tuple;
Console.WriteLine(Name1);
作为方法返回值(最常用场景)
替代 out 参数或自定义实体类,快速返回多个结果:
static (bool Success, string Message, int Code) Login(string username, string password)
{
if (username == "admin" && password == "123456")
{
return (Success: true, Message: "登录成功", Code: 200);
}
else
{
return (Success: false, Message: "用户名或密码错误", Code: 401);
}
}
调用方法1
var (isSuccess, msg, code) = Login("admin", "123456");
Console.WriteLine($"登录结果:{(isSuccess ? "成功" : "失败")},消息:{msg},状态码:{code}");
调用方法2
var loginResult = Login("test", "123");
Console.WriteLine($"登录失败:{loginResult.Message}({loginResult.Code})");
作为泛型使用
//初始化字典并且添加初始值
Dictionary<int,(int a, float b ,string c)> dic = new Dictionary<int, (int a, float b, string c)>() { { 1,(a:11,b:20,c:"字符串") },{ 2,(a:20,b:10,c: "123")}};
//单独添加元素
dic.Add(1,(a:11,b:20,c:"字符串"));
//读取元素
var tuple1 = dic[1];
Console.WriteLine(tuple1.a);
Console.WriteLine(tuple1.b);
Console.WriteLine(tuple1.c);
foreach (var item in dic)
{
//输出元组的值
Console.WriteLine(item.Value.a);
Console.WriteLine(item.Value.b);
Console.WriteLine(item.Value.b);
//输出Key值
Console.WriteLine(item.Key);
}
C# 预处理器指令
含义:
预处理器指令是指编译器在实际编译开始之前对信息进行预处理。通常用于简化源程序在不同的执行环境中的更改和编译
1.所有的预处理器指令都是以标识符 # 开始
2.预处理器指令不是语句,因此它们不需要以分号;结尾
预处理指令符号:
C# 预处理器常用指令列表
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真。
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。
举例理解:
在程序调试和发布程序时,有些代码是只在调试时才执行 在程序发布时就可能被注释 但是通常我们不用注释的方式来解决 而是用预处理指令完成
定义符号 #define 和 取消定义符号 #undef 可用于 #if 等编译指令的条件
//要写在文件开头
#define DebugModel
#undef DebugModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
#if DebugModel
Console.WriteLine("调试模式");
#endif
Console.ReadKey();
}
}
条件编译
- #if:打开条件编译,其中仅在定义了指定的符号时才会编译代码。
- #elif:关闭前面的条件编译,并基于是否定义了指定的符号打开一个新的条件编译。
- #else:关闭前面的条件编译,如果没有定义前面指定的符号,打开一个新的条件编译。
- #endif:关闭前面的条件编译。
- 条件编译指令的用法和 C# 中的条件判断语句 if、elif 和 else 语句差不多
#define condition2 // 定义 condition 字符
using System;public class ExampleProgram
{
static void Main(string[] args)
{
#if (condition)
Console.WriteLine("condition is defined");
#elif (condition2) // 测试 condition2 是否为真
Console.WriteLine("condition2 is defined");
#else
Console.WriteLine("condition is not defined");
#endif
Console.ReadLine();
}
}
定义区域符号 #region 和 #endregion 分别表示启动区域和结束区域。这两个预处理器指令来定义可在大纲中折叠的代码区域
错误和警告信息
#error:使用指定的消息生成编译器错误
#warning:使用指定的消息生成编译器警告。
static void Main(string[] args)
{
// 错误:此方法中的弃用代码。
#error 以下代码即将废弃
Console.WriteLine("你好");
#warning 以下代码即将废弃
Console.WriteLine("你好");}
#line:更改用编译器消息输出的行号
#line 200 "文件名" int i; int j; #line default char c; float f; #line 200 指令将下一行的行号强制设为 200 #line default 指令将行号恢复至默认行号
预处理器指令的用途
有利于项目的调式和运行。例如说可以使用条件编译指令控制程序流的执行,在实际的项目中表现为多版本代码片段控制。
在代码的调式阶段,可以使用错误和警告信息指令来禁止编译不属于本功能的额外代码。
使用定义区域指令可以很好折叠和隐藏指定区域的代码片段。开发者可以更好的集中处理关键代码,在有着多个代码区域的项目十分的方便。
C#中的IO(Input/Output)概念
操作包括读取和写入文件、读取和写入流、以及操作目录和文件夹等。这些操作都可以通过System.IO命名空间中的类实现。
封装IO功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ConsoleApp3_3_IO
{
internal class IOManager
{
//文件创建
//@"D:\myFile1234.txt"
public static bool CreateFile(string path)
{
try
{
//判断文件是否存在
if (!File.Exists(path))
{
//创建文件 在当前目录下
FileStream fileStream = File.Create(path);
//关闭文件流
fileStream.Close();
return true;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return false;
}
//文件写入内容 覆盖文件
public static bool WriteFile(string path,string content)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
// 1.File.WriteAllText(FilePath,String)
//2.File.WriteAllLines(FilePath,String[])
File.WriteAllText(path, content);
return true;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return false;
}
// File读取文件所有内容
public static string ReadFileAll(string path)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
// 1.File.ReadAllText(FilePath)
//2.File.ReadAllLines(FilePath)
return File.ReadAllText(path);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
// File读取文件所有内容
public static string[] ReadFileLine(string path)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
return File.ReadAllLines(path);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
StreamReader和StreamWriter类
StreamReader和StreamWriter类用于读取和写
// 通过文件流写入 覆盖文件
public static bool WriteFileByStream(string path,string content)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
//using 自动管理流的关闭和释放
using (StreamWriter streamWriter = new StreamWriter(path))
{
// 写入内容
streamWriter.Write(content);
// 写入内容并换行
streamWriter.WriteLine(content);
}
//手动管理流的关闭和释放
//streamWriter.Close();
//streamWriter.Dispose();
return true;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return false;
}
// 通过文件流读取 文件所有内容
public static string ReadFileByStream(string path)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
//using 自动管理流的关闭和释放
using (StreamReader streamReader = new StreamReader(path))
{
// 读取内容
streamReader.ReadLine();
return streamReader.ReadToEnd();
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
// 通过文件流读取 文件每一行
public static List<string> ReadFileLineByStream(string path)
{
try
{
//判断文件是否存在
if (File.Exists(path))
{
List<string> list = new List<string>();
//using 自动管理流的关闭和释放
using (StreamReader streamReader = new StreamReader(path))
{
// 读取内容 写一次读一行
//streamReader.ReadLine();
//streamReader.ReadLine();
//streamReader.ReadLine();
//streamReader.ReadLine();
string line = "";
while ((line =streamReader.ReadLine()) != null)
{
list.Add(line);
}
return list;
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
FileStream与StreamWriter 追加数据
// 通过文件流追加写入 如果文件不存在则创建文件并写入
public static bool WriteFileByStreamAppend(string path,string content)
{
try
{
if (!File.Exists(path))
{
//创建写入
using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
using (StreamWriter streamWriter = new StreamWriter(fileStream))
{
streamWriter.WriteLine(content);
}
}
}
else
{
//追加写入
using (FileStream fileStream = new FileStream(path, FileMode.Append, FileAccess.Write))
{
using (StreamWriter streamWriter = new StreamWriter(fileStream))
{
streamWriter.WriteLine(content);
}
}
}
return true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return false;
}
}
}
File 其他操作
删除
string newFilePath = @"E:\myFile_new.txt";
File.Delete(newFilePath);
移动
string sourceFilePath = @"E:\myFile.txt";
string destinationFilePath = @"D:\myFile.txt";
File.Move(sourceFilePath, destinationFilePath);
复制
string sourceFilePath = @"E:\myFile.txt";
string destinationFilePath = @"E:\myFile_copy.txt";
File.Copy(sourceFilePath, destinationFilePath);
Directory
C# Directory 常用功能
命名空间:`using System.IO;`
`Directory` 是静态类,直接调用方法;返回 `DirectoryInfo` 是实例对象
## 1. 创建目录
// 创建目录,多级不存在会自动创建
Directory.CreateDirectory(@"D:\test\a\b");
## 2. 判断目录是否存在
bool exists = Directory.Exists(@"D:\test\a\b");
if(exists)
{
//目录存在
}
## 3. 删除目录
// 只能删除空文件夹
Directory.Delete(@"D:\test\a\b");
// 删除包含里面所有文件和子文件夹
Directory.Delete(@"D:\test\a\b", true);
## 4. 获取子目录列表
// 获取该目录下一级所有子文件夹完整路径
string[] dirs = Directory.GetDirectories(@"D:\test");
// 搜索匹配,搜索所有子层级
string[] dirsAll = Directory.GetDirectories(@"D:\test", "*", SearchOption.AllDirectories);
## 5. 获取目录下所有文件
//一级目录下文件完整路径
string[] files = Directory.GetFiles(@"D:\test");
//筛选后缀,递归全部子文件夹
string[] txtFiles = Directory.GetFiles(@"D:\test", "*.txt", SearchOption.AllDirectories);
```
## 6. 移动目录(剪切)
//源文件夹移动到目标路径,目标不能已存在
Directory.Move(@"D:\old", @"D:\new");
## 7. 获取 / 设置目录信息
//获取当前程序运行目录
string curDir = Directory.GetCurrentDirectory();

1723

被折叠的 条评论
为什么被折叠?



