【C#】学习笔记
Solution & Project / 解决方案 & 项目
Java从整体上理解为一个工程或者项目,依赖是通过Jar包或者构建工具来实现引入的
C# 不同,抽象为一个解决方案,是一个解决具体事物的方案,不笼统的称为工程和项目
而在解决方案下,是一个个项目,解决方案可以引入其他项目或者动态库资源来实现依赖引入
Namespace 命名空间
相比Java是通过包名管理类资源
C# 基于命名空间来管理类资源
一个CS文件下声明一个命名空间,可以声明多个类,相对Java的包管理要灵活宽松
一般命名空间的规范会对应目录名,也可以自定义其他名称
Getter & Setter 的语法糖
声明类及其属性的getter&setter
class BusEntity { private int id; private string code; private string type; public int Id { get { return id; } set { id = value; } } public string Code { get { return code; } set { code = value; } } public string Type { get { return type; } set { type = value; } } }
调用时通过方法进行设置和获取
Extend & Implement / 继承和实现
继承和实现的方式统一是在类的后面写:,然后加上类名
/* Person 人.类 */ class Person { public string age; } /* Learn 学习.接口 */ interface Learn { void learn(string things); } /* 同时要继承和实现时,被继承的类写第一个,接口写在后面,不分顺序 */ internal class Student : Person, Learn { public uint id; public string name; public void learn(string things) { Console.WriteLine("learn {0}", things); } public void showInfo() { Console.WriteLine("student info {0}, {1} ", id, name); } }
Delegate 委托
这个东西主要是应用于函数式编程
方法和函数可以被参数化,Java的实现通过声明FunctionalInterFace类型的接口来实现
C# 是直接声明一个delegate类,这个类直接以声明抽象方法的方式来实现
声明学生类和一个展示信息方法,该方法无参数无返回值
internal class Student { public uint id; public string name; public void showInfo() { Console.WriteLine("student info {0}, {1} ", id, name); } }
在执行逻辑时,可以通过Action类委托进行调用:
Student student1 = new Student(); Student student2 = new Student(); Action action1 = new Action(student1.showInfo); Action action2 = new Action(student2.showInfo); action1(); action2();
委托可以编排一个调用列表
语法糖 += 是添加委托对象到调用列表, -= 是从委托对象删除委托对象
将action2添加到action1的调用列表里面
执行时,action1 和 action2 按添加的顺序进行调用
Student student1 = new Student(); Student student2 = new Student(); bool isEqual = student1.Equals(student2); Console.WriteLine("student1.id -> " + student1.id + " student1.name -> " + student1.name); Console.WriteLine("student2.id -> " + student2.id + " student2.name -> " + student2.name); Action action1 = new Action(student1.showInfo); Action action2 = new Action(student2.showInfo); action1 += action1; action1 += action2; action1();
Event 事件
在WPF中可以创建按钮并声明按钮名
<UserControl x:Class="WPF_Prism.Views.ViewA" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WPF_Prism.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="我是ViewA" FontSize="30" /> <Button Name="btn1" Grid.Row="1" Content="触发" Height="30" Width="200"></Button> </Grid> </UserControl>
初始化逻辑时,可以对按钮声明委托方法,简写Lambda方法后,入参发送器和事件参数
事件对象的传递基于委托办法来实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WPF_Prism.Views { /// <summary> /// ViewA.xaml 的交互逻辑 /// </summary> public partial class ViewA : UserControl { public ViewA() { InitializeComponent(); this.btn1.Click += (sender, RoutedEventArgs) => { Console.WriteLine("sender, RoutedEventArgs"); }; } } }
自定义事件委托
1 声明委托函数,方法声明 传递一个顾客对象和下单事件
2 声明下单事件类,事件属性设置 菜名和份量
3 声明顾客类,顾客类设置委托方法属性,并且需要设置事件的Add + Remove 方法,顾客的行为是 进入餐厅,就坐,点单,买单
4 服务员类,服务员设置下单方法,对应委托方法
/* 声明一个下单的委托函数,不限制是静态方法还是实例方法 */ public delegate void OrderEventHandler(Customer customer, OrderEventArgs orderEventArgs); /* 声明下单事件类,事件类需要继承事件参数类 */ public class OrderEventArgs : EventArgs { public string DishName { get; set; } public string Size { get; set; } } /* 声明顾客类 */ public class Customer { private OrderEventHandler orderEventHandler; /* 事件 必须要声明 add 和 remove 方法 */ public event OrderEventHandler Order { add { orderEventHandler += value; } remove { orderEventHandler -= value; } } public decimal Bill { get; set; } public void PayTheBill() { Console.WriteLine(" I will pay ${0}. ", this.Bill.ToString()); } public void WalkIn() { Console.WriteLine("Walk into the restaurant"); } public void SitDown() { Console.WriteLine("Sit down "); } public void Think() { for (int i = 0; i < 3; i++) { Console.WriteLine("Start thinking ..."); Thread.Sleep(1000); } /* 当下单事件处理器,即回调方法 */ if (orderEventHandler != null) { OrderEventArgs e = new OrderEventArgs(); e.DishName = "fried chicken"; e.Size = "large"; orderEventHandler(this, e); } } public void Action() { Console.ReadLine(); WalkIn(); SitDown(); Think(); } } public class Waiter { /* 服务员的Action方法,对应委托方法 */ public static void Action(Customer customer, OrderEventArgs eventArgs) { Console.WriteLine(eventArgs); Decimal price = 10; switch(eventArgs.Size) { case "small": price = Decimal.Multiply(price, (Decimal) 0.5); break; case "large": price = Decimal.Multiply(price, (Decimal) 1.5); break; default: break; } customer.Bill += price; Console.WriteLine("Create customer Bill"); } }
main方法执行时:
Customer customer = new Customer(); Waiter waiter = new Waiter(); customer.Order += Waiter.Action; customer.Action(); customer.PayTheBill();
打印输出
委托属性简化声明,不需要语法糖简化声明Getter & Setter
编译器会默认生成处理
public event OrderEventHandler Order; // private OrderEventHandler orderEventHandler; ///* 事件 必须要声明 add 和 remove 方法 */ //public event OrderEventHandler Order //{ //add { orderEventHandler += value; } //remove { orderEventHandler -= value; } //}
为什么要声明event关键字?
移除event关键字后,Order委托可以被随意调用
Customer customer = new Customer(); Waiter waiter = new Waiter(); customer.Order += Waiter.Action; customer.Action(); OrderEventArgs e1 = new OrderEventArgs() { DishName = "酸辣土豆丝", Size = "small" }; OrderEventArgs e2 = new OrderEventArgs() { DishName = "土豆烧牛腩", Size = "large" }; customer.Order.Invoke(customer, e1); customer.Order.Invoke(customer, e1); customer.PayTheBill();
event 关键字是为了限定委托方法的调用,添加后在外部只允许被添加和删除调用列表方法
customer.Order += Waiter.Action;
构造器与析构函数
1 静态构造器
在C#编程语言中,静态构造器(Static Constructor)是一个专门用于初始化类的静态成员的特殊构造器。与实例构造器(Instance Constructor)不同,静态构造器不是为创建类的实例而设计的,而是在类的任何静态成员被访问之前,由.NET运行时(CLR)自动调用的。
以下是静态构造器的一些关键特性和使用注意事项:
- 命名与访问:
- 静态构造器没有访问修饰符(如
public
、private
等),也不能有参数。 - 它们的命名遵循类名后跟一对圆括号的格式,例如
static MyClass()
。
- 静态构造器没有访问修饰符(如
- 调用时机:
- 静态构造器在类的任何静态成员被访问之前被自动调用,且只被调用一次,无论创建了多少个类的实例。
- 如果类从未被引用过(即没有任何静态成员被访问,也没有创建任何实例),则静态构造器将不会被调用。
- 用途:
- 静态构造器通常用于初始化静态数据成员或执行类级别的设置操作。
- 它们也可以用于在类首次使用前加载外部资源或执行其他必要的初始化工作。
- 异常处理:
- 如果静态构造器抛出异常,则会导致程序终止,并且该类的任何静态成员都无法再被访问。
- 这是因为静态构造器在类的整个生命周期中只执行一次,如果失败,则无法恢复。
- 继承:
- 派生类的静态构造器在基类的静态构造器之后被调用。
- 如果基类没有静态构造器,则派生类的静态构造器在首次访问派生类的任何静态成员时被调用。
- 线程安全:
- 静态构造器是线程安全的,.NET运行时确保在任何静态成员被访问之前,静态构造器只会被一个线程调用一次。
- 不可显式调用:
- 静态构造器不能被显式调用,它们是由.NET运行时自动管理的。
public class MyClass { // 静态数据成员 private static readonly string staticData = InitializeStaticData(); // 静态构造器 static MyClass() { // 执行静态初始化代码 Console.WriteLine("Static constructor called."); } // 静态初始化方法 private static string InitializeStaticData() { // 初始化静态数据并返回 return "Initialized static data"; } // 静态方法,用于演示访问静态成员 public static void PrintStaticData() { Console.WriteLine(staticData); } } // 使用示例 class Program { static void Main(string[] args) { // 访问静态成员,触发静态构造器的调用 MyClass.PrintStaticData(); } }
2 析构函数
在C#中,析构函数(Destructor)是一个特殊类型的方法,它用于在对象的生命周期结束时执行清理操作。与构造器(Constructor)不同,析构函数不是由程序员显式调用的;相反,它是由.NET运行时(CLR,Common Language Runtime)自动调用的,通常是在垃圾回收器(Garbage Collector, GC)确定对象不再被使用时。
析构函数的几个关键点如下:
-
命名约定:析构函数的名称是在类名前面加上波浪号(
~
)。例如,如果类名是MyClass
,那么析构函数的名称就是~MyClass()
。 -
自动调用:析构函数不能被显式调用或重载。当垃圾回收器检测到某个对象不再有任何活动引用时,它会自动调用该对象的析构函数(如果有的话)。
-
执行时机:析构函数的执行时机是不确定的,因为它依赖于垃圾回收器的运行。垃圾回收器在内存压力或其他触发条件下运行,并且可能会延迟析构函数的调用。
-
用途:析构函数通常用于释放非托管资源,如文件句柄、数据库连接或网络连接等。然而,对于托管资源(即其他.NET对象),推荐使用
IDisposable
接口和using
语句来管理资源的释放。 -
不可继承:析构函数不能被继承。每个类都必须定义自己的析构函数(如果有必要的话)。
-
终结器:在某些上下文中,析构函数也被称为终结器(Finalizer)。这个术语在.NET文档中偶尔会出现,但析构函数是更常用的术语。
-
性能考虑:由于析构函数的调用依赖于垃圾回收器,并且垃圾回收过程本身是有代价的,因此应该尽量避免依赖析构函数来管理资源。相反,应该实现
IDisposable
接口,并在适当的时候显式地释放资源。
class MyClass : IDisposable { // 托管和非托管资源 private bool disposed = false; // 构造器 public MyClass() { // 初始化资源 } // 析构函数 ~MyClass() { // 清理非托管资源 // 注意:不要在这里释放托管资源,因为析构函数的执行时机是不确定的 Dispose(false); } // 实现IDisposable接口 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // 阻止垃圾回收器再次调用析构函数 } // 受保护的Dispose方法,带有布尔参数以指示是否被Dispose方法调用 protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // 释放托管资源 } // 释放非托管资源 disposed = true; } } }
3 类的访问级别
在C#中,类的访问级别决定了哪些代码可以访问该类。C#提供了几种不同的访问级别来控制类的可访问性,主要包括public
、internal
(默认,如果不指定访问修饰符)、protected internal
(较少使用,因为通常类不会被继承为受保护的),以及组合使用这些修饰符的情况(尽管直接组合protected
和public
是不允许的)。
然而,对于顶级类(即不是嵌套在其他类内部的类)来说,常用的访问级别主要是public
和internal
。
以下是关于C#中类的访问级别的详细说明:
- public:
public
类可以在程序的任何地方被访问。- 它们可以被其他任何程序集(即编译后的.dll或.exe文件)引用和访问。
- 这是创建库或框架时常用的访问级别,以便其他开发者可以使用你的类。
- internal(默认):
- 如果不指定访问修饰符,类默认为
internal
。 internal
类只能在同一程序集内部被访问。- 它们不能被其他程序集引用或访问。
- 这是创建应用程序内部使用的类时常用的访问级别,以便隐藏实现细节并减少命名空间污染。
- 如果不指定访问修饰符,类默认为
- protected internal(对于类来说较少使用):
- 这个访问级别允许类在同一程序集中的任何位置以及派生类中访问。
- 然而,由于类通常不会被继承为受保护的(即它们不会被设计为只能由特定基类或接口派生的类),因此这个访问级别在顶级类上较少使用。
- 它更多地用于成员(如方法、属性等),而不是整个类。
- 其他注意事项:
- 顶级类不能是
private
或protected
的,因为这意味着它们将无法被任何外部代码访问,从而失去了存在的意义。 - 嵌套类(即定义在其他类内部的类)可以具有任何有效的访问级别,包括
private
和protected
。 - 访问级别应该根据类的用途和预期的使用场景来选择。如果你希望类被其他程序集使用,那么应该使用
public
;如果你希望类仅在内部使用,那么应该使用internal
(或默认不指定访问修饰符)。
- 顶级类不能是
总之,在C#中,类的访问级别是控制其可见性和可访问性的重要机制。通过选择合适的访问级别,你可以更好地组织代码、隐藏实现细节、减少命名空间污染,并确保你的类以期望的方式被使用。
4 反射
using System.Reflection; namespace CSharpBasic { class Program { static void Main(string[] args) { // See https://aka.ms/new-console-template for more information Console.WriteLine("Hello, World!"); /* 获取类的类型对象 */ Type studentType = typeof(Student); /* 通过类的类型可以创建类实例 */ object student = Activator.CreateInstance(studentType); Console.WriteLine(student); /* 类类型对象也可以通过实例获取 */ studentType = student.GetType(); /* 获取成员信息,成员包括了方法和字段? */ //MemberInfo[] memberInfos = studentType.GetMembers(); //foreach (MemberInfo memberInfo in memberInfos) //{ // Console.WriteLine(memberInfo); //} /* 获取方法信息 */ Console.WriteLine(" - - - 获取方法信息 - - - "); MethodInfo[] methodInfos = studentType.GetMethods(); foreach (MethodInfo methodInfo in methodInfos) { Console.WriteLine(methodInfo); } /* 获取方法并调用 */ MethodInfo method = studentType.GetMethod("aaa"); method.Invoke(student, null); /* 获取属性信息 */ Console.WriteLine(" - - - 获取属性信息 - - - "); PropertyInfo[] propertyInfos = studentType.GetProperties(); foreach (PropertyInfo propertyInfo in propertyInfos) { Console.WriteLine(propertyInfo); } } } class Student { public int Id { get; set; } public string Name { get; set; } public Student() { } public void aaa() { Console.WriteLine("---"); } } }
5、结构体与类的区别:
在C#编程语言中,结构体(struct)和类(class)都是用户定义的类型,用于封装数据和行为。尽管它们有许多相似之处,但也有一些重要的区别。以下是结构体和类之间的主要区别:
- 继承:
- 类:类可以继承另一个类(单继承),并且可以实现多个接口。
- 结构体:结构体不能继承另一个结构体或类,但可以实现接口。结构体隐式继承自
System.ValueType
,这意味着它们是值类型。
- 默认值:
- 类:类的实例在未初始化时,其成员变量默认为它们的默认值(如
null
、0
、false
等),但引用类型的成员变量默认是null
。 - 结构体:结构体的实例在未被显式初始化时,其所有成员变量都会被自动初始化为它们的默认值(如
0
、false
等)。
- 类:类的实例在未初始化时,其成员变量默认为它们的默认值(如
- 内存分配:
- 类:类是引用类型,存储在堆上。
- 结构体:结构体是值类型,存储在栈上(除非它们是类的成员变量、被装箱或存储在堆上的数组中)。
- 实例化:
- 类:类的实例必须使用
new
关键字进行显式创建。 - 结构体:结构体的实例可以在不使用
new
关键字的情况下进行创建,例如赋值给结构体的变量时,会自动进行默认初始化。然而,推荐还是显式使用new
关键字以确保初始化的一致性。
- 类:类的实例必须使用
- 不可变性:
- 类:类的成员变量可以是可变的,除非显式地实现不可变性。
- 结构体:由于结构体是值类型,它们通常更适合用于表示不可变数据。虽然可以修改结构体成员,但每次修改都会创建结构体的新副本。
- 使用场景:
- 类:通常用于表示复杂的行为和状态,以及需要继承的场合。
- 结构体:通常用于表示轻量级的数据集合,如点、矩形、颜色等,它们通常较小且不可变。
- 构造函数:
- 类:可以有无参数构造函数、有参数构造函数、析构函数等。
- 结构体:也可以有无参数构造函数和有参数构造函数,但不能有析构函数。