类:类就是拥有相等功能和相同的属性的对象的集合。
类是C#类型中最基础的类型。类是一个数据结构,将状态(字段)和行为(方法和其他函数成员)组合在一个单元中。类提供了用于动态创建类实例的定义,也就是对象(object)。类支持继承(inheritance)和多态(polymorphism),即派生类能够扩展和特殊化基类的机制。
如将哺乳动物划分为一类,然后人就可以作为哺乳动物的派生类,对哺乳动物的特点继承,对于人区分与其他哺乳动物的内容进行重写,实现多态。
定义类
定义类的语法为使用关键字 class
语法为
访问修饰符 class 类名
{
类的成员
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
class A
{
}
static void Main(string[] args)
{
}
}
}
这里的 class A 就是一个最简单的类
然后就可以在里面添加内容,对类A完善。
类的成员
类中包含大量成员,主要分为数据成员和函数成员。
类别 | 说明 | |
---|---|---|
数据成员 | 常量 | 与该类相关联的常数值 |
字段 | 该类的变量 | |
函数成员 | 方法 | 实现由该类执行的计算和操作 |
属性 | 定义类中的值并对他们进行读写{get;set;} | |
事件 | 说明发生了说明事 | |
索引器 | 允许像使用数组那样为类添加路径列表 | |
运算符 | 定义表达式运算符,通过他们对该类的实例进行运算 | |
实例构造函数 | 规定在初始化该类的时候需要做什么 | |
静态构造函数 | 规定在初始化该类自身时需要做什么 | |
私有构造函数 | 保证类不被实例化 | |
构析函数 | 规定永久的放弃该类的一个实例之前需要做什么 | |
类型 | 该类的局部类型 |
数据成员
数据成员
常量使用 const 关键字声明。在声明后变为常数值,且不能改变。
而字段作为与类相关的变量不需要关键字。
语法为 const 数据类型 常量名 ** =** 值;
在创建常量时必须对常量赋值,而字段不需要,但也可以。
语法为 数据类型 变量名;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
const int x = 10;//定义整形常量x为10
string s = "Hello World!";//定义字段可以赋值
bool p;//也可以不赋值
}
static void Main(string[] args)
{
}
}
}
函数成员
方法
在类中,方法分为实例方法和静态方法。
静态方法使用 static 关键字修饰。
方法的语法规则为:
修饰符 返回的类型 方法名 (参数)
{
方法体
}
修饰符表示可访问的权限。
返回的类型表示在方法体中 return 返回的数据类型,无返回值用 void。
方法名自定义,但希望遵守所有首字母大写的原则。
参数可选,可以没有参数,直接使用空括号;参数数量可变,可以有多个参数。
方法体自定义,为方法要实现的目的撰写代码。可以有多行。在非 ** void**返回类型的方法中要有 return ,且只有一个 return。
方法示例:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
public static int F1()//无参数静态方法
{
return 777;
}
public static int F2(int x)//有参数静态方法
{
return x;
}
public void F3()
{
WriteLine("000");//无参数无返回值实例方法
}
public string F4(string x)//有参数有返回值实例方法
{
return x;
}
public bool F5()//函数内可以有多行
{
WriteLine("为真");
return true;
}
}
static void Main(string[] args)
{
}
}
}
参数
方法可以有0个,1个或多个参数。
对于0个参数的方法,直接使用空括号。
对于有固定的数量的参数的方法,依次在括号内写入参数。
对于参数数量不固定的方法,需要使用 params 关键字表明要传入多个参数,该关键字只能放在最后一个参数后面,且只能有一个可变参数。
参数可以在定义时传入默认值,在使用的时候如果不传入参数可以直接使用默认值。但是在设置有默认值参数时必须将设有默认值的参数放在末尾(params除外,参数不固定必须放在末尾)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
public int F1()//无参数方法
{
return 777;
}
public int F2(int x)//一个参数方法
{
return x;
}
public int F3(int x1,int x2 = 100,params int[] ls1)//固定数量参数方法,且含有默认值
//这里传入一个可变长度参数数组ls1,使用关键字params,可变长度参数只能一个且放在末尾
{
return x1 + x2;
}
public int F4(int x1,bool x2,string x3,double x4)//传入参数类型不必统一
{
return x1;
}
public int F5(int x1,int x2)
{
return x1 / x2;//通过改变传入参数顺序改变结果
}
}
static void Main(string[] args)
{
var s = new A();
int a1 = s.F1();
int a2 = s.F2(777);
int a3 = s.F3(9, 10, 1, 2, 3, 4);//如果不传入第二个参数10将使用默认值100
int a4 = s.F4(1, true, "asd", 3.14);//尽管只返回其中一个参数,但要传入所有参数
int a5 = s.F5(x2:10, x1:100);//使用冒号指定传入参数名称从而改变传入参数顺序
//需要对所有参数表示名称
WriteLine(a1);
WriteLine("{0}\n{1}\n{2}\n{3}",a2,a3,a4,a5);
ReadKey();
}
}
}
传参
在传入参数的时候需要对所有参数传入,有默认值除外。
传入参数时有默认值时不传入参数使用默认值。
如果要改变传入参数顺序需要对参数使用 : 指定名称。
且指定名称时所有传入参数都要指定名称。
注意在命名传参时如果遇到不定长参数只能传入这个不定长参数的其中一个值。
对于内容体为一行的方法存在简略写法。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
public int F1()//完整写法
{
return 777;
}
public int F2() => 777;//简略写法
}
static void Main(string[] args)
{
ReadKey();
}
}
}
这里的方法F1和F2的结果是相同的,使用 => 省略花括号,以及 return。
但是注意这里后面需要写分号。
方法的调用
方法调用区分为两种类型。
一种为实例方法,一种为静态方法。
对于实例方法在调用其中的函数时需要对类进行创建实例对象。实例化可以使用 new关键字。
使用 var 定义一个变量,使其赋值为类的对象。
语法通常为 var 变量名 = new 类名();
然后对通过类的实例对象的变量名调用方法。
语法为 变量名.方法名(参数);
对于静态方法可以直接调用。
语法规则为 类名.方法名(参数)
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
public static void F() => WriteLine("静态方法");
public void FF() => WriteLine("实例方法");
}
static void Main(string[] args)
{
A.F();
var b = new A();
b.FF();
ReadKey();
}
}
}
关于静态方法与实例方法区别
静态方法需要使用 static 修饰,实例方法不用。
静态方法不需要进行创建对象直接就可以调用,实例方法需要创建对象。
实例方法可以访问类中的任何成员,而静态方法只能访问静态成员。
对于类的每个实例都有自己的实例方法,共享静态方法属于类本身,所有实例共享。
静态方法效率上要比实例化高,静态方法的缺点是不自动进行销毁,而实例化的则可以做销毁。
方法的重载
重载就是对方法的多态性体现之一,对于相同方法名的方法,通过改变参数实现对同名方法的不同操作。
方法的一个版本有不同的签名,即方法名相同,但是参数个数和/或数据类型不同。
实现方法的重载只需要声明同名但不同参的方法即可。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class A
{
public int F()//无参数
{
return 777;
}
public int F(int x)//一个整形参数
{
return x;
}
public string F(string x)//一个字符串参数
{
return x;
}
public string F(string x1,string x2)//两个字符串参数
{
return x1 + x2;
}
}
static void Main(string[] args)
{
ReadKey();
}
}
}
属性
属性是一个方法或一对方法,对于客户端来说是一段代码。
其目的为字段的读写属性标注。
属性是一个访问器,包含读 get 和写 set。
属性可以为私有字段提供外部访问。
get 访问器不带任何参数,且返回属性声明的类型。
set 访问器不应该指定任何显示的参数,但比以前会假定它带有一个参数,其类型也与属性相同,表示为value。
private int age;//私有字段age
public int Age//公有字段Age,为私有字段提供访问
{
get { return age; }//读取age
set { age = value; }//设置age的值
}
自动实现属性
如果属性的 get 和 set 中不存在其他逻辑,就可以自动实现属性,这种属性会自动实现读写。
public int Age {get;set;}
编译器会自动生成其字段的私有字段,但是无法直接访问,因为我们不知道编译器自动生成的名称。
但这样的属性无法验证属性的有效性。
public int Age {get;set;} = 42;
自动实现的属性可以初始化。
修饰属性
属性是可以被修饰的,可以在 get 和 set 中添加逻辑和修饰符。
private int age;
public int Age
{
get
{
if (age > 100)//为读取数据添加逻辑判断
{
return 0;
}
else
{
return age;
}
}
private set//更改写权限
{
age = value;
}
}
只读只写
属性或索引器至少有一个访问器。
如果属性只写一个 get 就会变成只读,只写属性不能自动实现。
public int X { get; set; }//读写都行
public int Y { get; }//只写
private int z;
public int Z//只写属性不能自动生成
{
set { z = value; }
}
事件
事件:基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
具体内容暂时略
索引器
索引器(Indexer) 允许一个对象可以像数组一样被索引。为类定义一个索引器时,该类的行为就会像一个 虚拟数组(virtual array) 一样。可以使用数组访问运算符([ ])来访问该类的实例。
具体内容暂时略
运算符
在类中可以对运算符重载,使用关键字 operator 。
可以使用用户自定义类型的运算符。重载运算符是具有特殊名称的函数,是通过关键字 operator 后跟运算符的符号来定义的。与其他函数一样,重载运算符有返回类型和参数列表。
其语法为 :
返回类型 operator 运算符 (参数)
{
return 返回值
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
int weight;//字段重量
public MyClass(int weight)//构造函数
{
this.weight = weight;
}
public static int operator - (MyClass a, MyClass b)//对运算符减号进行了更改
//将其更改为返回相加的和
{
return a.weight + b.weight;
}
}
static void Main(string[] args)
{
MyClass myClass1 = new MyClass(500);
MyClass myClass2 = new MyClass(200);
Console.Write(myClass1 - myClass2);//700
ReadKey();
}
}
}
构造函数
构造函数分为三类: 实例构造函数 、静态构造函数 和 私有构造函数。
构造函数是类的一种特殊方法,每次创建类的实例都会调用它。在创建一个类的实例时,构造函数就像一个方法一样被调用,但不返回值。
其语法为:
public class MyClass
{
public MyClass()
{
}
}
一般情况下,如果没有提供任何构造函数,编译器会在后台默认生成一个构造函数。
将所有成员字段初始化为标准的默认值。
默认值引用类型为空,数值类型为0,布尔类型为false。
如果为构造函数提供参数,参数为成员字段。编译器将不会自动提供默认的构造函数,编译器会假定这是唯一可用的默认,其他未列入参数的成员字段将不会初始化为默认值。
一个类中可以有多个构造函数,且构造函数可以重载
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public int a;
public int b;
public int c;
public int d;//没有构造函数,无法实例化
public MyClass(int a)//声明a的构造函数 {
a = 0;
}
public MyClass(int b,int c)//构造函数重载
{
b = 0;
c = 10;//设置初始值
}
}
static void Main(string[] args)
{
ReadKey();
}
}
}
···
在声明了传入参数的构造函数后,生成实例对象时需要传入参数。
···using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public int a;
public int b;
public MyClass(int a,int b)
{
a = 0;
b = 0;
}
}
static void Main(string[] args)
{
var s = new MyClass(1,2);
WriteLine($"{s.a} {s.b}");//0 0
//创建初始化对象,但是传入参数没有对a,b赋值,所以a,b现在还是0
s.a = 100;
s.b = 777;
WriteLine($"{s.a} {s.b}");//100 777
ReadKey();
}
}
}
实例构造函数
使用 new 表达式创建类的对象或者结构(例如int)时,会调用其构造函数。并且通常初始化新对象的数据成员。 new 关键字调用构造函数。
构造函数可以有参数,可以以重载的形式存在多个构造函数。
除非类是静态的,否则会为没有构造函数的类,自动生成一个默认构造函数,并使用默认值来初始化对象字段。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public int a;
public int b;
public MyClass(int a, int b)
{
this.a = a;//this 关键字表示当前对象,可以在创建实例对象时传入参数直接赋值
this.b = b;
}
}
static void Main(string[] args)
{
var x = new MyClass(1,10);
WriteLine(x.a);//0
WriteLine(x.b);//10
ReadKey();
}
}
}
this 关键字表示当前对象,可以在创建实例对象时传入参数直接赋值,避免了直接刚刚初始化默认值后传入参数无效。
构造函数也可以不传入参数。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public int a;
public int b;
public MyClass()
{
this.a = a;
this.b = b;
}
}
static void Main(string[] args)
{
var x = new MyClass();//无参数,直接使用默认值
WriteLine(x.a);//0
WriteLine(x.b);//0
ReadKey();
}
}
}
这里直接输出默认值0。
静态构造函数
可以给类编写无参数的静态构造函数,这种构造函数只执行一次。
编写静态函数是为了类中的静态字段或属性需要从外部初始化这些静态字段和属性。
静态函数无法预计在某时某刻以什么顺序执行。
但是可以确保静态函数之多运行一次。通常在第一次调用类的任何成员之前执行静态函数。
静态构造函数没有任何访问修饰符,也不能带有任何参数,一个类中只能有一个静态构造函数。
无参数实例构造函数和静态构造函数在一个类中定义。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public static int a;
public MyClass()//实例构造函数
{
a = 100;
}
static MyClass()//静态构造函数
{
a = 777;
}
}
static void Main(string[] args)
{
WriteLine(MyClass.a);//777
var x = new MyClass();
WriteLine(MyClass.a);//100
ReadKey();
}
}
}
这里首先输出777,在类中其他成员执行前先执行静态构造函数,然后生成实例对象,实例执行构造函数,使其值变成100。
私有构造函数
私有构造函数是一种特殊的实例构造函数。 它通常用于只包含静态成员的类中(保证该类不会被实例化)。 如果类具有一个或多个私有构造函数而没有公共构造函数,则无法创建该类的实例。声明空构造函数可阻止自动生成默认构造函数。
如果不声明公有,则默认为私有构造函数。
私有构造函数无法被实例化。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp6
{
class Program
{
public class MyClass
{
public static int a = 100;
private MyClass() { }//用空的私有构造函数使其无法实例化
}
static void Main(string[] args)
{
var x = new MyClass();//会报错,Program.MyClass.MyClass()”不可访问,因为它具有一定的保护级别
ReadKey();
}
}
}
析构函数
析构函数只能存在于类中,析构函数不存在任何修饰符,没有参数,也就是不能对析构函数重载。
析构函数在对象被垃圾回收器回收时调用。
析构函数的名字由符号“~”加类名组成。
public class Myclass
{
~Myclass()
{
}
}
一个类只能有一个析构函数,没有修饰符,没有参数,没有返回值。
析构函数与构造函数区别
构造函数是在创建对象时,使用给定的值将对象初始化。
析构函数用于释放对象,根据内存清楚策略执行。
类型
匿名类型
匿名类是继承自object且没有名称的类,类的定义从初始化器中推断。
创建匿名类型使用 var 和 new 关键字。
···
···
访问修饰符
说明 | 解释 |
---|---|
public | 声明公有成员,访问不受限制,允许从外部访问。 |
private | 声明私有成员,只有该类中的成员可以访问,如果在声明中没有设置访问修饰符,则默认是private。 |
protected | 声明受保护成员,包含类和包含类派生的类可以访问 ,对外界是隐藏的。 |
internal | 声明内部成员,只能当前程序集可以访问。 |
protected internal | 声明受保护的内部成员,只能访问当前程序集和包含类派生的类。 |
只读成员
如果不希望初始化化后修改数据成员,可以使用 readonly 修饰符。
只读字段:带有 readonly 修饰符的字段只能在构造函数中分配值。
不用与常量,只读字段可以是实例成员。使用只读字段作为类成员时,需要将static 分配给该字段。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
class MyClass
{
public readonly static int x;
}
static void Main(string[] args)
{
MyClass.x = 100;//报错
WriteLine(MyClass.x);
ReadKey();
}
}
}
在主函数中对x赋值时会报错。
无法对静态只读字段赋值(静态构造函数或变量初始值除外)。
{
public readonly static int x;
static MyClass()
{
x = 777;
}
}
class MyClass
{
public readonly static int x = 100;
}
}
用静态构造函数赋值,或者定义时直接赋值。
如果类型包含可以改变的成员,它就是一个可变类型,使用 readonly 修饰符会报错。
状态只能在构造函数中初始化,如果对象没有任何可以改变的成员,只有只读成员,那么就是一个不可变类型。
其内容只能在初始化时设置。
字段属性
最简单的写法就是
public int x {get;}
省略 set 访问器。变成只读属性。
自动创建只读属性。
public string id = Guid.NewGuid().ToString();
等号后面为此生GUID实例,返回值。
匿名类型
var 关键字用于表示隐式类型化的变量,和 new 一起使用的时候可以创建匿名类型。
匿名类型只是一个继承自object且没有名称的类。
编译器为类型伪造一个名称,只有编译器才能使用它。
var asdw = new
{
a = "111";
b = "222";
c = "333"
}
var qwe = new
{
a = "999";
b = "888";
c = "777"
}
这里的asdw和qwe的类型就是相同的。可以设置相等。
asdw = qwe;
结构
结构实际上将数据项组合在一起,大多数情况用public
类存储在堆中,数据在生存周期上有很大灵活性,但是性能会有一定损耗。
相对于的,可以使用结构。
结构和类的语法大致相同。但也有许多不同。
结构可带有方法、字段、索引、属性、运算符方法和事件。
例如,结构的定义使用关键字 struct
public struct MyStruct
{
public int x1;
public int x2;
}
public class MyClass
{
public int x1;
public int x2;
}
机构和类的区别
结构是值类型,类是引用类型。
结构存储在栈上,类存储在堆上,栈中保存的只是引用。
结构不支持继承,类支持继承。
结构不能声明默认的构造函数。
结构可实现一个或多个接口。
结构成员不能指定为 abstract、virtual 或 protected。
结构体中声明的字段无法赋予初值,类可以。
结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制。
使用场景
当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些。
在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
需要为每个对象分配内存,在这种情况下,使用结构的成本较低; 用于小的数据结构。
结构是值类型
虽然是值类型,但是可以当做类处理。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct MyStruct
{
public int x1;
public int x2;
}
static void Main(string[] args)
{
MyStruct a = new MyStruct();
a.x1 = 100;
a.x2 = 777;
WriteLine(a.x1);
WriteLine(a.x2);
ReadKey();
}
}
}
这里的 new运算符不分配内存,而只是调用相应的构造函数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct MyStruct
{
public int x1;
public int x2;
}
static void Main(string[] args)
{
MyStruct a;
a.x1 = 100;
a.x2 = 777;
WriteLine(a.x1);
WriteLine(a.x2);
ReadKey();
}
}
}
将 new 去掉也是合法。
因为结构是值类型,声明变量a是为结构在栈中分配空间。所以可以为其赋值。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct MyStruct
{
public int x1;
public int x2;
}
static void Main(string[] args)
{
MyStruct a;
int s = a.x2;
s = 100;
WriteLine(s);
ReadKey();
}
}
}
这里将会报错,原因出在 int s = a.x2;。
结构遵循其他数据类型都遵守的规则:所有元素在使用前都要初始化。
结构调用 new 运算符或者给所有字段赋值,结构就完全初始化了。
如果结构定义类的成员字段,在初始化包含对象时,该结构就会初始化为0。
结构会影响性能的值类型,根据结构的使用方式可能是正面的也可能是负面的。
正面:为结构分配内存时,速度非常快,因为他们保存在内联或栈中,在结构超出作用域被删除时也十分快,不需要等待了解回收。
负面:只要把结构作为参数传递时或赋值给另一个结构时,结构的所有内容都会被复制。而对于类只是复制引用。
把结构作为参数传递给方法时,应该作为 ref 参数传递。
此时只传递结构在内存中的地址,但是被调用的方法可以改变机构的值。
结构和继承
结构不能继承。
唯一例外的是所有结构都派生与system object。
因此结构可以访问 system object 的方法。
结构的构造函数
为结构定义构造函数的方法和为类定义构造函数的方法相同。
默认构造函数把所有数值字段都初始化为0,且总是隐式的给出。
结构的构造函数需要初始化每个数据成员。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct MyStruct
{
public int x1;
public int x2;
public MyStruct(int x1, int x2)//为每个成员初始化
{
this.x1 = x1;
this.x2 = x2;
}
}
static void Main(string[] args)
{
ReadKey();
}
}
}
按值按参数传递参数
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct A
{
public int X;
public A(int x)
{
X = x;
}
}
public static void ChangeA(A a)
{
a.X = 2;
}
static void Main(string[] args)
{
A a1 = new A(1);
ChangeA(a1);
WriteLine(a1.X);//1
ReadKey();
}
}
}
最后得到的结果为1,ChangA方法传入参数a1,而这个a1是一个值,是栈中变量的a一个副本,只会将a1.X变成2.。而对于原本的a.X没有影响。
如果A是类,则会传入引用,a1得到的是内存地址,直接对a操作。
如果要传入结构的引用,可以使用 ref 关键字
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
class Program
{
public struct A
{
public int X;
public A(int x)
{
X = x;
}
}
public static void ChangeA(ref A a)
{
a.X = 2;
}
static void Main(string[] args)
{
A a1 = new A(1);
ChangeA(ref a1);
WriteLine(a1.X);//2
ReadKey();
}
}
}
在ChangeA函数的参数前面指明要传入引用,所以在传入参数时也要指定是ref。
c#对传递给方法的参数必须要求初始化,无论是值还是引用
如果方法返回一个值,该方法通常声明返回类型,如果方法返回多个值,可能类型还不相同。有三种解决方法。
1.声明类和结构,将返回的所有信息都定义为该类型的成员。
2.使用元祖类型。
3.选用 out 关键字。
out关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。
out参数不能初始化并且必须在方法返回之前赋值而ref参数必须初始化。
若要使用 out参数,方法定义和调用方法都必须显式使用 out 关键字。例如:
class Outt
{
static void Method(out int i)
{
i=44;
}
static void Main()
{
Method(out value);
}
ref 和 out 关键字在运行时的处理方式不同,但在编译时的处理方式相同。因此,如果一个方法采用 ref 参数,而另一个方法采用 out 参数,则无法重载这两个方法。
但是,如果一个方法采用 ref 或 out 参数,而另一个方法不采用这两类参数,则可以进行重载。
属性不是变量,因此不能作为 out 参数传递。
可空类型
引用类型可以为空,而值类型不能为空。
可空类型只需要在需要的类型后面添加 ?
int? a = null;
对于可空类型,可以使用基本类型的运算符。
枚举
枚举是值类型,包含一组命名的常量,,默认类型为int,常量的值默认从0开始递增,可以改为其他值,但总是递增的,
使用 ** enum** 关键字
public enum f
{
q,
w,
e
}
可以更改为其他类型
public enum f:short
{
q,
w,
e
}
部分类
partial 允许把类,结构,方法或接口放在多个文件中。
partial 有助于把类分开放在两个文件中,而不对代码进行生成器定义的文件进行修改。
partial 用法是把 partial 放在class,struts或 interface关键字前面。
//asdw.cs
partial class MyClass1
{
}
//qwe/cs
partial class MyClass2
{
}
编译包含这两个源文件的项目时会创建一个 SampleClass 类,包含两个方法, MyClass1() 和 MyClass2() 。
如果声明了以下关键字,则这些关键字就必须应用于同一个类的所有部分。
public private protected internal abstract sealed new 一般约束
在嵌套的类型中,只要 partial 关键字位于class关键字前面,就可以嵌套部分类。
把嵌套了编译到类型中,属性,XML注释,接口,泛型的参数属性和成员会合并。
尽管 partial 关键字可以很容易创建跨过多个文件巨大的类,但最好把大类拆分为小类,一个类自用于一个目的。
部分类可以包含部分方法,如果生成的代码应该调用可能不存在的方法,这就是非常有用的,扩展部分类的人员可以决定创建部分方法的自定义实现方法,或者什么都不做。
部分方法的实现可以放在部分类的任何其他地方。部分方法必须是void类型,否则编译器在没有实现代码的情况下无法删除调用。
扩展方法
扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。它们的第一个参数指定该方法作用于哪个类型,并且该参数以 this修饰符为前缀。
继承就是给对象添加功能的好方法,扩展方法是给对象添加功能的另一个选项。
在不能继承时也可以使用这个方法,例如类是密封的。
扩展方法也可以用于扩展接口,这样,实现接口的所有类就有了公共功能。
扩展方法是静态,是类的一部分,但实际上没有放在类的源代码中。
即使扩展方法是静态,也要使用标准的实例方法语法,在后台编译器会把它改为静态方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApp8
{
public static class Extension // 必须是一个静态类
{
public static void SayHello(this string str) //必须为public static 类型
//且参数使用this关键字
{
if (str == null)
{
Console.WriteLine("Hello");
}
else
Console.WriteLine("{0}", str);
}
}
class Program
{
static void Main(string[] args)
{
string str = null;
str.SayHello();
string str1 = "not hello";
str1.SayHello();
Console.ReadKey();
}
}
}
这样的话在同一个命名空间下的所有string都可以使用SayHello方法了.
首先声明扩展方法的类必须是static类,并且方法也要是static类型,扩展方法必须包含this关键字作为它的第一个参数类型,并且后面跟着它所扩展的类的名称.
this 关键字必须匹配类型的扩展方法,而且需要打开定义扩展的静态方法的名称空间。
如果类型还定义了同名的实例方法,扩展方法就永远不会使用,类中的已有实例方法都优先。
当多个同名的扩展方法扩展相同的类型,编译器无法在多个实现代码中选择,但是如果调用代码在同一个名称空间时,这个名称空间就优先。
object类
**所有的.Net类都最终派生于system object。
在定义类时没有指定基类就会自动假定这个类派生与system object。
对于结构,派生是间接的。
结构总是派生与system vauletype,而system vauletype 又派生与system object。
tostring() 获取当前对象字符串的快捷方式。
gethashcode() 获取映射
equals()和referenceequals() 等价性比较。
finalize() 垃圾回收
gettype() 获取类型
memberwiseclone() 浅复制