今日份主要内容:
1.接口
2.结构,可空类型Nullable
3.新语法ref struct和ref return
4.枚举
5.枚举类型作为位标志
6.枚举练习,应用程序配置
7.日期DateTime
接口
接口定义一套其它类实现它的规则。契约,一种合同。一种定义了一组相关方法、属性和事件的合同,用于实现类来遵循。
接口:使用java、asp.net等编写的API接口。让其他人通过相应的请求协议(http/https)来访问。理解成“在接口服务器上定义一个方法,在客户端上调用此方法,这样一个过程。”
在C#中,接口和抽象类都用于定义一种抽象的方法,
本质上也是一种方法,里面存了一些成员,这些成员也都是方法。接口必须实现才能用。接口也是多态性的表现。
事件就是一种行为,被动的,必须被触发调用才能进行。
在C#中类只能继承一个父类,单继承,不支持多继承,但类可以实现多个接口。
- 如果一个类,同时实现两个接口,和一个父类,顺序是啥?
- 类在实现接口和继承父类同时存在时,先继承再去实现。
-
接口可以单独定义一个文件,也可以定义在cs里,跟类一样。建议:接口命名一般建议使用大写。
-
私有的接口没有意义,它只能是public或者internal。接口中的成员默认都是公开的,关键字public必须省略。
-
接口中可以包含类的常用成员(如:属性,方法,事件),但并不是类中的所有成员都可以在接口中存在。
public interface IBank
{
public Interface1(){} //接口中不能有构造函数。
private string fieled; //接口中不能有字段。
//规律:通过类的实例访问的成员,在接口里统统不能存在。原因:接口不能实例化,只能被其它类实现。
//规律:接口中一般不能出现静态的成员。
static string Property{get; set;}
//属性成员,默认公开
string BankName{get; set;}
//方法成员,默认公开,不能实现,这点和抽象方法类似
//方法成员
void Pay();
}
public class NYBank : IBank
{
private string _bankName = "农业银行";
string IBank.BankName { get => _bankName; set => _bankName = value; }
void IBank.Pay()
{
Console.WriteLine("农业银行转账!");
}
//接口在实现时有两种方式:1. 隐式实现(常用) 2. 显示实现
public calss JSBank : IBank,IJin
{
//alt + 回车键 +回车键。或者点小灯泡。可以默认实现接口或抽象类。
//类在实现多个接口时,接口之间没有顺序。多个接口之间用逗号分隔,但类在实现接口和继承父类同时存在时,先继承再去实现。
public string BankName{ get; set; } = "建设银行";
public void Pay()
{
Console.WriteLine("建设银行转账!");
}
}
static void Main(string[] args)
{
//使用接口前必须实现接口
NYBank nybank = new NYBank(); //明确确定类型
nybank = new JSBank();
IBank bank = new NYBank();//还有切换类型的可能性,IBank被多个类实现
//bank = new JSBank();
bank.Pay();
IJijin jijin = new JSBank();
jijin.Sale(1000);
Console.ReadKey();
}
//基金接口
public interface IJijin
{
void Buy(decimal amount);
void Sale(decimal amount);
}
总结特点
接口:
- 只能定义公共的抽象方法、属性、事件和索引器
- 不包含任何实现。
- 可以被类直接实现。
- 支持多重继承(通过可继承接口的概念)
- 主要用于定义合同,规定类必须实现的行为。
抽象类:
- 可以包含抽象和非抽象方法,
- 抽象方法不包含实现,需要在子类中实现
- 抽象类不能直接实例化,只能作为基类被继承。
- 不支持多重继承,但可以通过继承多个接口来实现类似效果
- 提供了一种实现抽象的方式,也可以提供具体实现的细节。
- 类可以实现接口,接口还可以继承接口。
- 接口继承接口,然后进行扩展,可以继承多个接口。类不能继承多个类,可以实现多个接口。
- 可以继承接口里面的方法。
结构
结构使用struct关键字来定义。结构是值类型的根本,值类型本质就是用结构实现的。结构就是自定义的值类型。
比如:DataTime、Point(GDI+就是图形页面绘制)using System.Drawing。坐标,范围,日期,时间等。
- 类型:值类型,引用类型。
- 值类型在存取时性能高,原因:存储到栈(又称堆栈)
- 引用类型存储堆(托管堆)
值类型一般存储的数据量少,引用类型存储数据量大,且复杂。
- 那结构为什么还要让用户自定义呢?
- 说明结构也可以像类一样,存储大量且复杂的数据。(场景相对少一些,栈区资源比较珍贵。)
结构使用的场景:
1.一般存储少量且简单的数据结构,追求性能。(如:int,long,bool等)
2.存储大量且复杂的数据结构,且性能高(场景少一些)
个人理解:C#语言中,其实所有的类都可以转换结构,所有结构也可以转换类。
类和结构两个技术点,类比较灵活,结构受限制。
如果对性能要求不高,就去使用类。现在设备好,不用太追求性能。
定义结构的目的就是为了追求性能。
//关键字struct
public struct Book
{
//结构中不能有无参构造函数
//public Book(){ }
//结构中可以包含有参构造函数
public Book(int id, string name, float price)
{
this.id = id;
this.Name = name; //报错,完整的属性写法和构造函数不能共存。
this.Price = price;
}
//结构中也可以有属性,方法,事件,索引器等这些类中的成员。但结构里面必须给私有字段在构造函数里赋值。
public int Id{ get; set; } //语法糖写法正确,可以用于构造函数。
//结构中一般不建议使用完整的属性,原因:字段在结构中不能初始化。
//完整的属性,结构里面字段不能初始化。
private string name = "张三";
public string Name
{
get{return name;}
set{name = value;}
}
public float Price{ get; set;}
public void Read(int page)
{
Console.WriteLine($"{this.Name}已经被读了{page}页");
}
//~Book(){Console.WriteLine()} 结构里不可以使用析构函数。
}
static void Main(string[] args)
{
//结构使用的时候和类一样,可以new,new结构的实例时,没有调用无参构造函数。
//类new的时候,调用的是类的构造函数。
Book b = new Book();
Book b1 = new Book(1, "C#高级编程", 88.88F); // 调用有参构造函数
Book b2 = new Book() { Id = 2, Name = "XXX", Price = 28.88F };
Console.WriteLine(b1.Name);
b1.Read(3);
// 没有构造函数的结构如何初始化呢?跟类一样
CPoint cPoint1 = new CPoint() { X = 10, Y = 20 };
CPoint cPoint2 = new CPoint();
cPoint2.X = 100;
cPoint2.Y = 200;
// 结构可以不使用new操作符进行实例化
CPoint2 p;
p.X = 100;
p.Y = 200;
}
public struct CPoint2 //一般写法:私有的字段,公开的属性。
{
public int X;
public int Y;
}
// 没有构造函数的结构如何初始化呢?
public struct CPoint
{
public int X { get; set; }
public int Y { get; set; }
}
在 C# 中,属性和字段主要有以下一些区别:
字段:
- 是直接的数据存储位置。
- 通常是私有的,以保护数据的完整性和安全性。
属性:
- 提供了一种灵活的方式来访问和操作字段。
- 可以添加额外的逻辑,如数据验证、计算等。
- 可以是只读、只写或读写的,而字段一般是可读写的。
- 对外表现为类似字段的访问方式,但背后可能有更复杂的操作。
属性可以更好地封装数据,控制数据的访问和修改方式,增加代码的健壮性和灵活性。
可空类型Nullable
结构是值类型,值类型默认不能设置成null。
可空类型出现就是让值类型,可以设置为null,和引用类型没有关系。
Nullable<int> a1 = null; //int? = Nullable<int> 语法糖、完整的写法
CPoint? cPoint4 = null; //CPoint? == Nullable<CPoint>
Book? b3 = null;
b3 = new Book(); // 不是null,声明了(只分配空间),没有存储数据
if (b3 == null)
{
Console.WriteLine("成立");
}
else
{
// b3?.Id 变量b3不为null时,访问属性Id
// b3为null时,使用是Id的默认值
// 相当于if(b3!=null){Console.WriteLine(b3.Id); }
Console.WriteLine(b3?.Id);
}
Class1 cls = new Class1();
if (cls != null) { Console.WriteLine(cls.Id); }
Class1 cls2; // 默认值为null
//cls2.Id = 2; // null没有任何的成员
结构相关细节:
-
结构及结构体,是值类型数据结构。
-
结构使用 struct 关键字声明。
-
结构和类相比,一般保存的数据量少,存储在栈上,读取速度比类快。
-
结构可带有方法、字段、索引、属性、运算符重载和事件。
-
结构可定义有参构造函数,但不能定义析构函数和无参构造函数。
-
结构和类不同,不支持继承。但结构可以实现接口。
-
结构成员不能指定为 abstract、virtual 或 protected。
-
当您使用 new 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
-
如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
结构和类的区别:
- 在C#中,结构(struct)和类(class)是两种不同的数据类型,它们有以下区别:
- a.结构是值类型,而类是引用类型。这意味着结构直接包含其数据,而类的实例包含对其数据的引用。
- b.结构不支持继承,而类可以继承其他类。
- c.结构的实例化不需要调用构造函数,而类的实例化需要。
- d.结构的构造函数必须包括所有字段的赋值,而类的构造函数不需要。
- e.结构不能定义析构函数,而类可以。
- f.在方法参数传递时,结构是通过值传递的,而类是通过引用传递的。
- g.结构可以实现接口,而类也可以实现接口。
- h.结构默认不能初始化为null,但类可以。
- i.在性能上,大型结构或经常需要复制的结构使用类可能更为高效,因为类是引用类型,而结构是值类型,复制类的引用比复制结构的数据更加高效。
新语法ref struct和ref return
在 C# 中,ref struct 是一个特别的结构类型,可以确保实例只存在于栈上,而不是在堆上。这对于某些高性能场景非常有用,因为可以减少垃圾回收造成的开销。ref struct 是在 C# 7.2 中引入的,作为一种增强内存管理和效率的工具。
ref struct 的特性:
只能在栈上分配。不可在堆上分配。
不能作为类、普通结构或数组的成员。
不能作为其他 ref struct 的字段,除非该字段被标记为 ref。
不能被装箱或者转换为 Object、ValueType 或 System.Enum。
不能实现接口。
不能被用作闭包变量(也就是不能被捕获到 lambda 表达式或本地函数中)。
ref return(了解)
C# 中的 ref return 又称引用返回,它允许一个方法返回对象的引用而不是对象的值。这意味着返回的引用可以用来修改该对象。ref return 在 C# 7.0 中引入,【主要用于优化性能,特别是在处理大型结构时,因为它避免了值类型的复制】。
参考:C#入门(6): 结构体、ref struct_c# ref struct-CSDN博客
枚举
枚举是值类型,存在栈上,一般用来命名一组整型常量,使用enum(enumeration)关键字声明,不支持继承或传递继承。
static main()
{
public enum Direction : int
{
//一组整型常量(int),long也行,但很少用到。默认为int,:int可以不写上。
//枚举的项,不明确设置值的时,默认从0开始,其他的项依次加1
//枚举的项的值,可以随意设定(要求整型),但一般建议有规律。
//枚举的项,如果只给第一项设置值,其他项的值累加。
//枚举中的项没有顺序。
East = 0; //键值对,也可以叫名值对 key/value或name/value
South = -1,
West = 12,
North
}
//long类型的枚举一般不多
public enum MyEnum: long
{
}
//常量不能修改,使用const关键字,支持多种类型常量
const string north = "北";
const float PI = 3.1415926F;
//枚举不能实例化,直接通过枚举名称取其中一项。
Direction.WreiteLine(Direction.North); //取键(名字)
Direction.WreiteLine((int)Direction.North); //强转就可以取值
//参数1:枚举的类型, 参数2:枚举变量 这样也可以取键
string name = Enum.GetName(typeof(Direction),dire);
Console.WriteLine(name);
// Enum.Parse()可以把一个字符转换成某个枚举值,装箱“Object”,要强转回去。
Direction direction = (Direction) Enum.Parse(typeof(Direction), "West");
Console.WriteLine(direction);
if (direction == Direction.West)
{
Console.WriteLine("你选择West");
}
Direction dire = Direction.north;
if(dire == Direction.North)
{
Console.WriteLine("你选择的为北方");
}
//加中括号访问的,都有索引器,集合就支持索引器。枚举没有索引,不可以Direction[]。
//枚举一次只能取一个,想要取多个要用别的方法。按位计算
// dire2取Direction这个枚举中任意一个, 值:多个枚举值按位或
Direction dire2 = Direction.North | Direction.West | Direction.East | Direction.South;
if (dire2 == dire)
{
Console.WriteLine("你选择是北方2222");
}
// 枚举中没有迭代器,不能循环。
/*foreach (var item in Direction)
{
}*/
Console.ReadKey(); //悬停一下。
}
枚举类型作为位标志
可以使用枚举类型定义位标志,从而使该枚举类型的实例可以存储枚举数列表中定义的值的任意组合。
枚举类型作为位标志,可以从一组枚举值中取任意组合(多个值)。
枚举类型作为位标志,可以让枚举项之间进行:AND &、OR |、NOT ~ 和 XOR ^ 按位运算。
特性:用来描述对象的,中括号[]的写法,比如[flag],为了将来在某个时刻,通过描述可以拿到某个对象的详细信息。这种处理方法的目的主要是为了解耦的,可以和反射结合用。可以把特性看成一个标签,给某个对象贴上标签,反射会使用特性,取出详细信息。
- 特性:贴标签;
- 反射:通过标签拿出信息,识别标签。
阅读:枚举类型(C# 编程指南) | Microsoft Learn
枚举练习,应用程序配置
在config里面配置,appsetting应用程序的配置节点。调用config需要添加引用,通过报错,或者搜索。先引用,再using,类库也这样调用。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<!--ctrl+k+c xml注释快捷键 应用程序配置 -->
<appSettings>
<!-- value有规律:枚举的值 -->
<add key="dire" value="东方"/>
<add key="week" value="Monday"/>
</appSettings>
</configuration>
public enum Dire { 东方, 南方, 西方, 北方 }
public enum Week
{
None,
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
using System.Configuration;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("请选择一个方向:[0]东\t[1]南\t[2]西\t[3]北");
string value = Console.ReadLine();
string name = Enum.GetName(typeof(Dire), int.Parse(value));// 根据枚举值拿枚举名称
Console.WriteLine($"你选择的方向:{name}");
// ConfigurationManager类, 配置管理器,管理配置文件(读写)。
string dire = ConfigurationManager.AppSettings["dire"]; // 从配置文件读,通过name读取
string week = ConfigurationManager.AppSettings[1].ToString(); // 通过索引读取
// 比较枚举名称
if (dire == name)
{
Console.WriteLine("你选择的方向和配置文件一致!!");
}
Dire d1 = (Dire)Enum.Parse(typeof(Dire), name);
Dire d2 = (Dire)Enum.Parse(typeof(Dire), dire);
// 比较枚举类型
if (d1 == d2)
{
Console.WriteLine("你选择的方向和配置文件一致222222!!");
}
Console.ReadKey();
}
}
日期DateTime
日期是常用的结构类型
internal class Program
{
static void Main(string[] args)
{
// 1。定义日期,日期组成:年,月,日,时,分,秒,毫秒,....
DateTime d1 = DateTime.Now; // 当前日期 UtcNow了解
DateTime d2 = new DateTime(2024, 7, 29); // 年月日
DateTime d3 = new DateTime(2024, 7, 29, 16, 5, 30, 999); // 年月日时分秒毫秒
DateTime d4 = new DateTime(2024, 7, 29, 16, 5, 30, DateTimeKind.Utc); // 年月日时分秒
DateTime d5 = new DateTime(DateTime.Now.Ticks); // 年月日时分秒 滴答声,刻度,周期数:1/100纳秒
Console.WriteLine(d1);
Console.WriteLine(d2);
Console.WriteLine(d3.ToString("yyyy-MM-dd HH:mm:ss.fff"));
Console.WriteLine(d4);
Console.WriteLine(d5);
Console.WriteLine(d3.Date); // yyyy-MM-dd
Console.WriteLine("-----------------");
// 2。使用日期
Console.WriteLine(d3.Year);
//Console.WriteLine(d3.Month.ToString().PadLeft(2,'0'));
string month = d3.Month.ToString();
if (d3.Month < 10) month = "0" + month;
Console.WriteLine(month);
Console.WriteLine(d3.Day);
Console.WriteLine(d3.Hour);
Console.WriteLine(d3.Minute);
Console.WriteLine(d3.Second);
Console.WriteLine(d3.Millisecond);
// 3。日期格式化,下方参考。
// 4。日期转换:1。日期转换成字符串(格式化) 2。字符串转换成日期
string strDate = "2025-7-29 16:24:30"; // 日期字符串,保证格式是正确
DateTime d6 = DateTime.Parse(strDate);
Console.WriteLine(d6.Year);
string strDate2 = "abc"; // 日期字符串,保证格式是正确
bool result = DateTime.TryParse(strDate2, out DateTime d7);
if (result)
Console.WriteLine(d7.Year);
else
Console.WriteLine("转换不成功!");
//DateTime d8 = Convert.ToDateTime(strDate2); // 报错
// 5。重要属性和方法
Console.WriteLine(d1.DayOfWeek); // 星期几 一周中第几天
DateTime d9 = new DateTime(2024, 1, 1);
Console.WriteLine(d9.DayOfYear); // 一年中的第几天
Console.WriteLine(DateTime.DaysInMonth(2024, 7)); // 一个月有几天
Console.WriteLine(d1.TimeOfDay); // 取时间部分 TimeSpan时间间隔
Console.WriteLine(DateTime.Today); // 今天 和DateTime.Date类似之处
Console.WriteLine(DateTime.Now); // 现在
// 了解即可
Console.WriteLine(DateTime.MinValue); // 0001/01/01.....
Console.WriteLine(DateTime.MaxValue); // 9999
// 掌握:添加年,天,月,时,分,秒,毫秒,刻度(周期数 1/100纳秒)
Console.WriteLine("--------------------");
Console.WriteLine(DateTime.Now.AddDays(1));
Console.WriteLine(DateTime.Now.AddDays(-1));
Console.WriteLine(DateTime.Now.AddDays(-1));
Console.WriteLine(DateTime.Now.AddHours(1));
Console.WriteLine(DateTime.Now.AddHours(-1));
// 可以同时添加,时,分,秒, 借助TimeSpan
Console.WriteLine(DateTime.Now.Add(new TimeSpan(2, 2, 2)));
DateTime result2 = DateTime.Now + new TimeSpan(2, 2, 2);
Console.WriteLine(result2);
Console.WriteLine("------------------");
Console.WriteLine(DateTime.Now.Subtract(new TimeSpan(2, 2, 2)));
DateTime result3 = DateTime.Now - new TimeSpan(2, 2, 2);
Console.WriteLine(result3);
Console.WriteLine("------------------");
// 日期比较:1。运算符 2。Compare(), CompareTo(), Equals()
DateTime d10 = new DateTime(2024, 1, 1);
if (d10 < DateTime.Now)
{
Console.WriteLine("成立");
}
if (!d10.Equals(DateTime.Now))
{
Console.WriteLine("不成立");
}
Console.WriteLine(DateTime.IsLeapYear(2000));
Console.WriteLine(DateTime.IsLeapYear(1999));
Console.WriteLine(DateTime.Now.ToLongDateString());
Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(DateTime.Now.ToLongTimeString());
Console.WriteLine(DateTime.Now.ToShortTimeString());
Console.WriteLine(DateTime.Now.ToFileTime());
Console.WriteLine(DateTime.Now.Ticks);
Console.ReadKey();
}
}
参考:DateTime 结构 (System) | Microsoft Learn
自定义日期和时间格式字符串 - .NET | Microsoft Learn
标签:Console,C#,接口,DateTime,枚举,WriteLine,new From: https://www.cnblogs.com/dbsdb/p/18330759