基本概念
类和结构的重要区别:
- 类类型的对象通过引用传递,结构类型的对象按值传递。
2. 类类型存储在堆(heap),而结构类型存储在栈(stack)。
类中不能添加执行代码!
name="jack"
这样的代码我们称之为“执行代码”,意思就是说这些代码只有在被执行的时候才会有效果。
字段
字段是类的数据成员,它是类型的一个变量,该类型是类的一个成员。
var customer1 = new PjoneCustomer();
customer1.FirstName = "Simon";
只读字段
保证对象的字段不能改变,字段可以用readonly修饰声明。
用readonly修饰符声明字段,只允许在构造函数中初始化属性的值。
public class Docunment
{
private readonly DateTime _creationTime;
public Document()
{
_creationTime = DateTime.Now;
}
}
void SomeMethod()
{
s_maxDocuments = 10;//compilation error here.MaxDocuments is readonly
}
不可变的类型
如果对象没有任何可以改变的成员,只有只读成员,它就是一个不可变的类型。
String类:没有定义任何允许改变其内容的成员。(ToUpper的方法总是返回一个新的字符串,但传递到构造函数的原始字符保持不变)
常量
常量与类相关(没有static修饰符),编译器使用真实值代替常量。
方法
方法的声明
方法修饰符(访问性和返回值类型)+ 方法名 + 输入参数的列表
参数
表达式体方法
如果方法的实现只有一个语句,C#6为方法定义提供了一个简化的语法:表达式体方法。
语法:使用运算符“=>”(lambda操作符)区分左边的声明和操作符右边的实现代码。
namespace Csharp_test01
{
class Program
{
static void Main(string[] args)
{
WriteLine($"Pi is {Math.GetPi()}\n");
int x = Math.GetSquareOf(5);
WriteLine($"Square is {x}\n");
var math = new Math();
math.Value = 30;
WriteLine($"{math.Value}\n");
WriteLine($"{math.GetSquare()}\n");
}
}
}
public class Math
{
public int Value { get; set; }
public int GetSquare() => Value * Value;
public static int GetSquareOf(int x) => x * x;
public static double GetPi() => 3.14159;
}
方法的重载
方法名相同,但参数的个数或数据类型不同。为了重载方法,只需要声明同名但参数个数或类型不同的方法即可。
/*类型名不同的方法重载*/
class ResultDisplayer
{
public void DisplayResult(string result)
{
//implemention
}
public void DisplayResult(int result)
{
//implemention
}
}
/*数量不同的方法重载*/
class Myclass
{
public int DoSomething(int x)
{
return DoSomething(x,10);
}
public int DoSomething(int x,int y)
{
//implementation
}
}
命名的参数调用
调用方法时,变量名不需要添加到调用中。
语法:“变量名”:“Value”
注意:编译器会去掉变量名,创建一个方法调用,就像没有变量名一样。
例如:
class r
{
public void MoveAndResize(int x,int y,int width,int height)
}
MoveAndResize(30,20,20,40);
//也可以改变调用,明确数字的含义
MoveAndResize(x:30,y:40,width:20,height:40);
可选参数
参数是可选的;
注意:①必须为参数提供默认值;②可选参数必须是方法定义的最后的参数;
③编译器更改调用代码,填充所有的参数,如果以后添加另一个参数,早期的调用程序就会失败。
pubilc void TestMethod(int notOptionNumber,int optionalNumber = 42)
{
Writeline(optionalNumber + notOptionNumber);
}
TestMethod(11);
TestMethod(11,22);
个数可变的参数
有一种语法允许数量可变的参数(这个语法没有版本控制的问题)
public void AnyNumberOfArguments(params int[] data)
{
foreach(var x in data)
{
WriteLine(x);
}
}
/*该函数的参数类型是int[]
**可以传递int数组
**params关键字:可以传递一个或任何数量的值
*/
AnyNumberOfArguments(1,2,3,4);
将不同类型的参数传递给方法,可以使用object数组:
public void AnyNumberOfArguments(params objects[] data)
{
//etc
}
AnyNumberOfArguments("text",43);
注意:“params”关键字如果与其他多个参数一起使用,
① 则params”只能使用一次
② 必须是最后一个参数
属性
属性是可以从客户端访问的函数组,其访问方式与访问类的公共字段类似。
属性的作用
对字段进行赋值时加以限制,因本段代码字段是姓名所以没有限制。
public class Employee
{
//字段
private byte age;
//属性
public byte Age
{
get { return age; }
set {
if(value >= 18 && value<=60)
age = value;
}
}
}
属性与字段的区别
这段代码中声明了name字段和Name属性,一般来说属性名是变量名的首字母大写。
public class Employee
{
//字段
private string name;
//属性
public string Name
{
get { return name; }
set { name = value; }
}
}
只读属性
在属性定义中省略set访问器,就可以创建只读属性。(一般不建议)
自动实现的只读属性
pubulic string Id{get;} = Guid.NewGuid().ToString();
编译器会创建一个只读字段和一个属性,其get访问器可以访问这个字段。
不可变的类型
具有表达式体的属性访问器
private string _firstName;
public string FirstName
{
get => _firstName;
set => _firstName =Value;
}
//属性访问器的实现只能由一条语句组成
构造函数
构造函数是在实例化对象时自动调用的特殊函数。必须与所属的类同名,且不能有返回类型。用于初始化字段的值。
(1) 如果没有编写构造函数,编译器会生成一个默认构造函数,它会把成员字段初始化为标准的默认值;(例如:引用类型为空引用,数据类型为9,bool为false)
(2) 如果编写了带参数的构造函数,编译器就不会自动提供默认的构造函数;
(3) 构造函数的重载遵循与其他方法相同的规则。
如果将构造函数定义为private或protected,这样不想关的类就不能访问它们。
这样定义有两种情况下回使用:
(1) 类仅用于静态成员或属性的容器,因此永远不会实例化它。
(2) 希望类仅仅通过调用某个静态函数来实例化。
public class MyNumber
{
private int _number;
private MyNumber(int number)
{
}
}
public class Singleton
{
private static Singleton s_instance;
private int _state;
private Singleton(int state)
{
_state = state;
}
public static Singleton Instance
{
get { return s_instance ?? (s_instance = new Mysingleton(43)); }
}
}
/*
* 该类包含一个私有构造函数,只能在类中实例化它本身。
* 如果这个字段尚未初始化(null),就调用实例构造函数,创建一个新的实例。
*/
从构造函数中调用其他构造函数
l 一个类中可以包含几个构造函数,以容纳某些可选参数;
l 这些构造函数包含共同的代码;
class Car
{
private string _description;
private uint _nWheels;
public Car(string description,uint nWheels)
{
_description = description;
_nWheels = nWheels;
}
public Car(string decription):this(decription,4)
{
}
}
/*
* this 关键字仅调用参数最匹配的那个构造函数。
*/
假定运行下面代码:
var myCar = new Car("xxxxxx");
在本例中,在带一个参数的构造函数的函数体执行之前,先执行两个参数的构造函数。也可以包含对直接基类的构造函数的调用。
静态构造函数
静态构造函数只执行一次,其他的构造函数是实例构造函数,只要创建类的对象就会执行它。
在C#中,通常在第一次调用类的任何成员之前执行静态构造函数。
.NET运行库不能确保静态函数在特定时间执行静态构造函数,所以不能把关键代码放在静态构造函数中。
特点:
(1) 静态构造函数没有访问修饰符;
(2) 静态构造函数不能带任何参数;
(3) 一个类有且只有一个静态构造函数;
(4) 静态构造函数只能访问类的静态成员,不能访问类的实例成员。
用法:
namespace Csharp_test01
{
class Program
{
static void Main(string[] args)
{
WriteLine($"{Userpreferences.BackColor}");
}
}
}
public enum Color
{
white,
Red,
Green,
Blue,
Black
}
public static class Userpreferences
{
public static Color BackColor { get; }
static Userpreferences()
{
DateTime now = DateTime.Now;
if(now.DayOfWeek == DayOfWeek.Saturday
|| now.DayOfWeek == DayOfWeek.Sunday)
{
BackColor = Color.Green;
}
else
{
BackColor = Color.Red;
}
}
}
索引器
运算符
事件
事件是类的成员,在发生某些行为(修改类的字段或属性,进行了某种形式的用户交互操作)时,可以让对象通知调用方。
析构函数
类似于构造函数的语法,当CLR检测到不再需要某个对象时调用它。
类型
嵌套类
说明:嵌套类(nested class)完全封装(嵌套)在另一个类的声明中。
限制:嵌套类提供了一种方便的方法,让外部类能够创建并使用其对象,但在外部类的外面不能访问它们。
嵌套类的访问级别:
嵌套类的访问级别至少与包含它的类相同。例如:
嵌套类为 public,而包含它的类为internal,则嵌套类的访问级别默认也为 internal,只有所属程序集的成员能够访问它。
如果包含它的类为public,嵌套类遵循的访问级别规则将与非嵌套类相同。
分布类
说明:将类声明分成多个部分—通常存储在多个文件中。
分部类的实现方法与常规类完全相同,但在关键字class 前面有关键字partial。
静态类
静态类通常包含实用程序或辅助方法(helper method),它们不需要类实例就能 工作。
不可能创建静态类的实例
静态类只能包含静态成员,但这些成员并不会自动变成静态的,您必须显式地使用修饰符 static。然而,可将任何静态成员声明为 public、 private或internal的。
扩展方法是常规的静态方法,但第一个参数包含修饰符this,
该参数指定要扩展的类型,通常称为类型扩展参数 。
扩展方法:
扩展方法就是在不修改原有类源代码的情况下,添加方法。
为string类型扩展一个ToInt方法:
/// <summary>
/// 1、定义一个静态类
/// 2、静态类的名称和要实现扩展方法的具体类无关
/// </summary>
public static class SomeClass
{
/// <summary>
/// 3、实现一个具体的静态方法
/// </summary>
/// <param name="str">4、第一个参数必须使用this关键字指定要使用扩展方法的类型</param>
/// <returns></returns>
public static int ToInt(this string str)
{
return int.Parse(str);
}
}
匿名类型
var关键字,用于表示隐式类型化的变量,var与new关键字一起使用就可以创建匿名类型。
var captain = new {
FirstName = "James",
MiddleName = "T",
LastName = "Kirk"
};
结构体
结构是值类型,不是引用类型,存储在栈中或内联。
特点:
(1) 结构不支持继承;
(2) 如果没有提供默认的构造函数,编译器会自动提供一个,把成员初始化为其默认值;
(3) 使用结构,可以指定字段如何在内存中布局。
public struct Dimensions
{
public double Lengh{get;set;}
public double Width{get;set;}
public Dimensions(double Lengh,double width)
{
Length = length;
Width = width;
}
public double Diagonal => Math.Sqrt(Length * Length +Width * Width);
结构是值类型
语法上可以当做类处理。
var point = new Dimensions();
point.Length = 3;
point.Width = 6;
注意:这里的new并不分配堆中的内存,而是只调用相应的构造函数。(进行初始化)
也可以这样编辑,但是一定要初始化数据的value。
Dimensions point;
point.Length = 3;
point.Width = 6;
结构与继承
结构不能从另一个结构中继承。
唯一例外的是对应的结构最终派生于类System.Object。
每个结构都派生自ValueType。
结构的构造函数
public struct Dimensions
{
public double Length;
public double Width;
public Dimensions(double length,double width)
{
Length = length;
Width = width;
}
}
结构与对象的异同
语法上:
(1) 从语法上来看.它们的语法都大同小异,类里面的成员几乎都可以定义在结构体中,但是析构函数除外。
(2) 在结构体中可以声明字段,但是声明字段的时候是不能给初始值的;
(3) 结构体不能包含无参数的构造函数。
(4) 结构体的构造函数可以写任意的代码,在类中不能包含执行代码;
(5) C#语法规定在结构体的构造函数中,必须要为结构体的所有字段赋值。
(6) 在结构体的构造函数中不能为属性赋值,要直接为字段赋值。