(一)类和结构

类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。

类和结构的区别:内存中的存储方式、访问方式(类是存储在堆上的引用类型,结构是存储在栈的值类型)和它们的一些特征(如结构不支持继承)。

较小的数据建议使用结构来提高性能。

创建类使用 class 关键字

例子:

class ClassTest { }

创建结构使用 struct 关键字

例子:

struct StructTest { }

对于类和结构,都是用关键字new来声明实例:这个关键字创建对象并对其初始化

ClassTest classTest = new ClassTest();
StructTest structTest = new StructTest();

(二)类

类中的数据和函数成为类的成员。

1、数据成员

数据成员是包含类的数据——字段、常量和事件的成员。

例子:

以下代码创建了一个名为People的类,其中包含了一个常量Country和一个字段Name

class People {
public const string Country = "中国";
public string Name;
}

例子:

现在我们实例化People对象,可以使用Object.FieldName来字段,使用ClassName.ConstName来访问常量(因为常量总是静态)

static void Main(string[] args)
{
People people = new People();
people.Name = "张三";
Console.WriteLine("{0}的国家是{1}", people.Name, People.Country);
Console.ReadKey();
}

运行以上代码,结果如下:

2、函数成员

函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器、运算符以及索引器。

  • 方法是与某个类相关的函数,与数据成员一样,函数成员默认为实例成员,使用static修饰符可以把方法定义为静态方法。
  • 属性是可以从客户端访问的函数组,其访问方式与访问类的公共字段类似。
  • 构造函数是在实例化对象时自动调用的特殊函数。它们必须与所属的类同名,且不能有返回类型。构造函数用于初始化字段的值。
  • 终结器类似于构造函数,但是在CLR检测到不再需要某个对象时调用它。它们的与所属类同名,但前有一个“~”符号。无法预测什么时候调用终结器。
  • C#允许指定已有的运算符应用于自己的类(运算符重载)。
  • 索引器允许对象以数组或集合的方式进行索引。

(1)方法

正式的C#术语区分函数和方法。

1.方法的声明

在C#中,方法的定义包括任意方法修饰符(如方法的可访问性)、返回值的类型,然后依次是方法名、输入参数列表(用圆括号括起来)和方法体(用花括号括起来)。

例子:

public bool IsLeft(string direction)
{
if (direction=="left")
{
return true;
}
else
{
return false;
}
}

以上代码中,public是方法的可访问性,bool是返回值的类型,IsLeft是方法名,(string direction)是输入参数,{}内的是方法体

2.方法的调用

static void Main(string[] args)
{
Program program = new Program();
bool isLeft = program.IsLeft("left");
Console.ReadKey();
}
public bool IsLeft(string direction)
{
if (direction == "left")
{
return true;
}
else
{
return false;
}
}

以上代码,因为IsLeft不是静态方法,所以要实例化所在的类,然后通过实例对象调用IsLeft

3.给方法传递参数

参数可以通过引用或通过值传递给方法。

例子:

参数是值类型还是引用类型的区别

static void Main(string[] args)
{
int _i = 0;
int[] _ints = { 0 };
Console.WriteLine("运行方法前:_ints[0]={0},_i={1}", _ints[0], _i);
SomeFunction(_ints, _i);
Console.WriteLine("运行方法后:_ints[0]={0},_i={1}", _ints[0], _i);
Console.ReadKey();
}
static void SomeFunction(int[] ints, int i)
{
ints[0] = 100;
i = 100;
}

运行以上代码,结果如下:

4.ref参数

输入参数前带有 ref 关键字(使用 ref 的参数必须初始化),则该方法对变量所做的任何改变都会影响原始对象的值

static void Main(string[] args)
{
int _i = 0;
int[] _ints = { 0 };
Console.WriteLine("运行方法前:_ints[0]={0},_i={1}", _ints[0], _i);
SomeFunction(_ints,ref _i);
Console.WriteLine("运行方法后:_ints[0]={0},_i={1}", _ints[0], _i);
Console.ReadKey();
}
static void SomeFunction(int[] ints,ref int i)
{
ints[0] = 100;
i = 100;
}

运行以上代码,结果如下:

5.out参数

输入参数前面加上 out 关键字(可以不初始化),方法里把要输出的值直接赋给这个参数即可

static void Main(string[] args)
{
int _i;
SomeFunction(out _i);
Console.WriteLine("运行方法后:_i={0}", _i);
Console.ReadKey();
}
static void SomeFunction(out int i)
{
i = 100;
}

运行以上代码,结果如下:

6.命名参数

命名参数允许按任意顺序传递。

static void Main(string[] args)
{
FillName("张","三");
FillName(lastName:"三",firstName:"张");
Console.ReadKey();
}
static void FillName(string firstName, string lastName)
{
Console.WriteLine(firstName + " " + lastName);
}

运行以上代码,结果如下:

7.可选参数

当为参数提供了默认值时,参数就可选了。可选参数必须定义在参数列表的最后面

例子:

static void FillName(string firstName, string lastName="三")
{
Console.WriteLine(firstName + " " + lastName);
}

8.方法的重载

C#支持方法的重载——方法的几个版本有不同的签名(即,方法名相同,但参数的个数或类型不同)。

static void Show(string value)
{
Console.WriteLine(value);
}
static void Show(int value)
{
Console.WriteLine(value);
}

(2)属性

属性的概念是:它是一个方法或一对方法,在客户端代码看来,它们是一个字段。

例子:

以下代码定义了一个名为Age的属性

public int age;
public int Age
{
get
{
return age;
}
set
{
ageage = value;
}
}

我们采用C#区分大写的模式,使用相同的名称,但公有属性采用Pascal大小写形式命名,如果存在一个等价的私有字段,则它采用camel大小写形式命名。

1.只读和只写属性

在属性定义中省略set访问器,就可以创建只读属性;省略get访问器,就可以创建只写属性(一般不创建只写属性)

public int age;
public int Age
{
get
{
return age;
}
}

2.属性的访问修饰符

C#允许给属性的get和set访问器设置不同的访问修饰符,但必须有一个具备属性的访问级别。

public int age;
public int Age
{
get
{
return age;
}
private set
{
age = value;
}
}

3.自动实现的属性

如果属性的set和get访问器中没有任何逻辑,就可以使用自动实现的属性。

例子:

public int Age { get; set; }

使用自动实现的属性,就不能再属性设置中验证属性的有效性。虽然不能删掉get或set访问器,单可以给它们设置访问级别。

例子:

public int Age { get; private set; }

(3)构造函数

声明基本构造函数的语法就是声明一个与包含的类同名的方法,但是该方法没有返回类型。

例子:

以下代码,创建了一个名为People的类,然后在其中创建了其无参的构造函数(当我们不提供任何构造函数时,编译器会在后台默认创建一个无参构造函数)

class People
{
public People()
{ }
}

以下代码,创建了一个有参数的构造函数,这个时候编译器就不会默认创建一个无参构造函数

class People
{
string name;
public People(string Name)
{
name = Name;
}
}

1.静态构造函数

静态构造函数没有参数,只执行一次,且一个类只能有一个静态构造函数,而前面的构造函数都是实例构造函数,只要创建类的对象就会执行它。

例子:

以下代码创建People类的静态构造函数

class People
{
static People()
{
}
}

2.构造函数中调用其他构造函数

使用 this 关键字调用同一类中的其他构造函数

class People
{
public People(string firstName, string lastName)
{
Console.WriteLine(firstName + " " + lastName);
}
public People(string lastName):this("张",lastName)
{ }
}

使用 base 关键字可以调用基类中的构造函数

class PeopleBase
{
public PeopleBase(string firstName, string lastName)
{
Console.WriteLine(firstName + " " + lastName);
}
}
class People: PeopleBase
{
public People(string lastName):base("张",lastName)
{ }
}

3、只读字段

readonly关键字比const灵活得多,允许把一个字段设置为常量,但还需要执行一些计算,已确定它的初始值。其规则是可以在构造函数中给只读字段赋值,但不能在其他地方赋值。

例子:

class People
{
public readonly string Country;
public People(string firstName)
{
if (firstName == "约翰")
{
Country = "外国";
}
else if (firstName == "张")
{ Country = "中国";
}
}
}

(三)匿名类型

var与new关键字一起使用时,可以创建匿名类型。

例子:

以下代码创建了一个包含个人姓氏、名字和年龄的对象

var Person = new { firstName = "张", lastName = "三", age = "3" };

这个对象的类型名是编译器赋的一个我们无法使用名称,我们也不应该在类型名上去进行其他操作,因为会造成结果不同。

(四)结构

定义结构我们只需要用 struct 关键字替换类的 class 关键字即可

例子:

struct People
{
public string Name;
}

需要注意:

结构是值类型。

结构不支持继承。

结构的无参构造函数总是由编译器提供,不可替换。

使用结构,可以指定字段如何在内存中布局

1、结构是值类型

虽然结构是值类型,但在语法上完全可以把它当做类来处理。但也因为结构是值类型,new 运算符与类和其他引用类型的工作方式不同,结构的new运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段。

例子:

以下代码完全合法(假定People在这里是结构)

People people;
people.Name = "张三";

结构遵循其他数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。

在结构上调用new运算符,或者给所有字段分别赋值,结构就完全初始化了。

结构主要用于小的数据结构。

2、结构和继承

结构不是为继承设计的。这意味着:它不能从一个结构中继承。每个结构都派生自ValueType。

3、结构的构造函数

为结构定义构造函数的方式与类相同,唯一的区别是结构不允许定义无参的构造函数。结构的无参构造函数有编译器提供,其作用是把数值初始化为0,引用类型初始化为null,即使提供了其他构造函数也是如此。

(五)弱引用

在应用程序代码内实例化一个类或结构时,只要有代码引用它,就会形成强引用。弱引用是使用WeakReference类创建的。

例子:

 1 static void Main(string[] args)
2 {
3 WeakReference peopleReference = new WeakReference(new People());
4 People people;
5 if (peopleReference.IsAlive)
6 {
7 people = peopleReference.Target as People;
8 people.Name = "张三";
9 people.Age =12;
10 Console.WriteLine("{0}今年{1}岁",people.Name,people.Age);
11 }
12 else
13 {
14 Console.WriteLine("引用为空");
15 }
16 GC.Collect();
17 if (peopleReference.IsAlive)
18 {
19 people = peopleReference.Target as People;
20 }
21 else
22 {
23 Console.WriteLine("引用为空");
24 }
25 Console.ReadKey();
26 }

(六)部分类

partial关键字允许把类、结构、方法或接口放在多个文件中。

partial class  People
{
public string Name;
public int Age;
} partial class People
{
public void Show() {
Console.WriteLine("{0}今年{1}岁", this.Name, this.Age);
}
}

(七)静态类

如果类只包含静态的方法和属性,该类就是静态的。给类添加 static 关键字,编译器可以检查用户是否不经意间给该类添加了实例成员。

例子:

静态类的语法

static class People
{
public static string Name;
public static int Age;
public static void Show() {
Console.WriteLine("{0}今年{1}岁", Name, Age);
}
}

例子:

调用上面的静态类

static void Main(string[] args)
{
People.Name = "张三";
People.Age = 18;
People.Show();
Console.ReadKey();
}

(八)Object类

所有的.NET类都派生自System.Object。

1、System.Object()方法

  • ToString()方法:是获取对象的字符串表示的一种便捷方式。
  • GetHashCode()方法:如果对象放在名为映射(也称为散射或字典)的数据结构中,就可以使用这个方法。
  • Equals()方法和ReferenceEquals()方法:用于比较对象相等性。
  • Finalize()方法:Object中的Finalize()方法其实什么也没做。
  • GetType()方法:这个方法返回从System.Type派生的类的一个实例。这个对象可以提供对象成员所属类的更多信息,包括基本类型、方法、属性等。System.Type还提供了.NET反射的入口点。
  • MemberwiseClone()方法:复制对象,但只是一个浅表复制,即复制了所有的值,但引用对象赋值的是引用地址。

2、ToString()方法

它是快速获取对象的字符串表示的最便捷的方式。

例子:

static void Main(string[] args)
{
int i = 100;
string str = i.ToString();
Console.ReadKey();
}

运行以上代码,结果如下:

Obejct.ToString()声明为虚方法,所以可以在类中重写该方法

例子:

 1 class Program
2 {
3 static void Main(string[] args)
4 {
5 People people = new People();
6 people.Name = "张三";
7 people.Age = 18;
8 Console.WriteLine(people.ToString());
9 Console.ReadKey();
10 }
11 }
12
13
14 class People
15 {
16 public string Name;
17 public int Age;
18 public override string ToString()
19 {
20 return string.Format("{0}今年{1}岁", this.Name, this.Age);
21 }
22 }

运行以上代码,结果如下:

(九)扩展方法

如果有类的源代码,继承就是给对象添加功能的好方法。但如果没有源码,则可以使用扩展方法,它允许改变一个类,但不需要该类的源代码。

扩展方法是静态的,它是类的一部分,但实际上没有放在类的源代码中。

例子:

以下代码创建了People类的一个扩展方法

static class PeopleExtension
{
public static string AddCountryToPeople(this People people, string country)
{
return people.ToString() + "居住在" + country;
}
}

调用以上扩展方法

static void Main(string[] args)
{
People people = new People();
people.Name = "张三";
people.Age = 18;
Console.WriteLine(people.AddCountryToString("中国"));
Console.ReadKey();
}

运行以上代码,结果如下:

需要注意:

  • 调用扩展方法需要使用对象的实例。
  • 如果扩展方法名与源代码中的某个类名重复,那么扩展方法无效。

最新文章

  1. 使用Ado.net执行SP很慢,而用SSMS执行很快
  2. [DOM Event Learning] Section 1 DOM Event 处理器绑定的几种方法
  3. 群里分享的react的收藏一下!今日周末,改了个表单验证然后无所事事了!
  4. Gulp自动添加版本号(转载)
  5. WPF QuickStart系列
  6. 沈逸老师PHP魔鬼特训笔记(9)--进化
  7. php正则表达式判断是否为ip格式
  8. Intent实现页面跳转
  9. JavaScript中url 传递参数(特殊字符)解决方法
  10. iOS多线程总结(二)NSOperation
  11. mysql基础示例
  12. yum下载安装redis
  13. VMware 安装 centos6.8
  14. Web browser的发展史
  15. C. Nastya Is Transposing Matrices
  16. Apache Flume 学习笔记
  17. 洛谷.3690.[模板]Link Cut Tree(动态树)
  18. etcd 集群运维实践
  19. vue动画
  20. mac下 配置homebrew 和java home

热门文章

  1. cve_2019_0708_bluekeep漏洞
  2. 理解 Python 的 for 循环
  3. CF333E Summer Earnings
  4. 【问题解决】Axios调用文件下载获取不到文件名
  5. GET 请求和 POST 请求的区别和使用
  6. 算法竞赛进阶指南0x33同余
  7. IO概述(概念&分类)和字节输入流+OUTputStream类&FileOutPutStream类介绍
  8. python socket理解
  9. Nginx Lua拓展模块操作Redis、Mysql
  10. 高效简单的.Net数据库“访问+操作”技术