C# 语言初学者入门






1.92/5 (6投票s)
帮助 C# 语言初学者的文章
引言
以我有限的知识,我正在撰写本文,旨在向初学者阐明 C# 语言的某些基本要点。C# 中类的抽象概念区分了内置类型和用户定义类型。在 C# 中,您通过使用 class
关键字来创建新类型。类具有通过类的成员方法实现的行为,并且类具有状态,您可以使用成员变量(有时称为字段)对该状态进行建模。您通过属性提供对类状态的访问。在 C# 中,您区分了类(新类型的定义)和对象(该类的实例)。例如,Dog
是一个类(它描述了 Dog
类型),但 Rover
、Fido
和 Spot
都是对象:它们是 Dog
类的实例。同样,Button
是一个类,但在下面,Save
、Cancel
和 Delete
都是对象,是 Button
类的实例。
方法必须在其调用返回后才能执行下一行代码。例如
using System;
public class Application {
public static void Main() {
Console.WriteLine("Before the method call.");
SomeMethod();
Console.WriteLine("After the method call.");
}
static void SomeMethod()
{
Int32 x = 4 * 16;
Console.WriteLine(x);
}
}
导致
Before the method call.
64
After the method call.
这个极其简单的示例的重点是,代码包含三个静态方法。在 C# 中,Main
方法始终是 static
的。Static
方法是对 C 语言中“global
”方法概念的折衷。C# 没有全局方法,但有时能够在不拥有类的特定实例的情况下调用方法是很方便的。当您调用 WriteLine()
时,您不是在 Console 的实例上调用它,而是在 Console
类本身上调用它。因此,为了阐明实例
方法和静态
方法之间的区别,请考虑另一个用户定义类的简单示例
using System;
public class Employee {
// give the class two private members, one static and one not
private Int32 age;
static private Int32 numEmployees = 0; // keep tract of number of instances
// Create a constructor that initializes the instance variable. Have the
// constructor update the static member to indicate that another object has
// been created. Note that you must access the static member through the
// class, rather than through the instance:
public Employee(Int32 age)
{
this.age = age;
Employee.numEmployees++;
}
// You access the static member numEmployees with a static method. Add
// the following method to the class to do this
public static Int32 GetNumEmployees()
{
return numEmployees;
}
// Add an instance method to display information about the employee, and
// within that method, access the number of employees through the static
// method:
public void DisplayEmployeeInfo()
{
Console.WriteLine("This employee is {0} years old",age );
Console.WriteLine("{0} employees have been created",Employee.GetNumEmployees());
}
static void Main(string[] args)
{
Int32 age = 46;
Employee emp1 = new Employee(age);
emp1.DisplayEmployeeInfo();
Employee emp2 = new Employee(35);
emp2.DisplayEmployeeInfo();
Employee emp3 = new Employee(21);
emp3.DisplayEmployeeInfo();
}
}
// use the csc.exe compiler compile and run the program. Note that the static
// member is able to track the number of objects created
输出
This employee is 46 years old
1 employees have been created
This employee is 35 years old
2 employees have been created
This employee is 21 years old
3 employees have been created
您使用 new
运算符实例化一个对象,该类的实例。要调用非静态方法,必须有该类的一个实例。您通过使用 new 运算符并编写 new Employee()
来实例化 Employee
类的一个实例,我们将其称为 emp1
。当您编写 'new Employee()
' 时,您会获得一个对该对象的引用,该引用存储在 emp1
中。emp1
是数据类型 Employee
的变量。然后,您可以通过在该实例上使用点运算符来调用非静态方法,在本例中为 DisplayInfo()
。您在对象(类的实例)上调用实例方法,并在类本身上调用 static
方法。在 C# 中,static
方法在没有实例的情况下被调用。这意味着实例方法在实例上被调用。Static
方法在类上被调用。如前所述,Main
当然总是 static
的,但还有其他辅助方法可能在不方便拥有实例时是 static
的。例如,Conversion
类中有转换方法,例如 Convert.ToInt32()
,它将字符串转换为整数(DWORD
)。您宁愿不必实例化 Convert
类的一个实例来仅仅使用它的 Convert.ToInt32()
方法。与 static
方法类似的是 static
成员或字段。Static
成员属于类,而不是对象。它们不用于表示对象的状态,而是由类的所有实例共享。通常,这个概念用于在任何给定时间跟踪实例的数量。您可以使用 static
方法访问您的 static
成员,这使得我们的 static
成员是 private
的。请注意,员工数量被跟踪(通过查看输出)。在代码中,static private Int32 numEmployees
被初始化为零,并且在构造函数内部 Employee.numEmployees
被递增(numEmployees++
)。初学者还应该注意,构造函数的名称始终与类的名称相同。
再看看输出。该 static
成员跟踪已创建的员工数量。由于 C# 提供了垃圾回收,我们可以根据需要创建对象,然后当它们不再需要时,垃圾回收器会处理它们。据说对象已超出范围。需要注意的重要一点是,这个终结过程是非确定性的。也就是说,您无法知道何时声明该对象被终结以销毁。您无法决定或强制它。是 CLR 的垃圾回收器调用 finalize()
方法。您可能需要创建一个 finalize
方法,但仅当资源稀缺时。但这涉及的资源不仅稀缺,例如内存中的文件,而且它们还是非托管的。如果您正在处理托管资源,您可以调用 Dispose()
方法,但如果您已经打开了文件,您应该直接关闭文件,然后垃圾回收器会处理它。
“this” 关键字
在一个类中,'this
' 关键字指的是类的当前实例。它用于区分成员变量和参数。可以有一个成员变量,例如“age
”,以及构造函数的一个参数 age,您可以通过成员变量是 this.age
来区分它们。考虑一个类 Employee
,带有一个构造函数 Employee
public Employee (Int32 age, string name)
{
this.age == age;
this name == name;
}
我们向构造函数传递了两个参数(数据类型为 Int32
的 age 和数据类型为 string
的 name)。结果是 Employee
类也有两个成员变量,也称为 age
和 name
。我们可以通过编写 this.age = age
将参数 age 中的值赋给成员变量 age。this
关键字指的是当前对象。在构造函数内部,this
关键字指的是正在创建的对象。通常将 private
成员变量命名为与传递给构造函数的参数相同的名称;在这种情况下,您可以使用 this
关键字来区分成员变量和参数
public class Employee {
private Int32 age;
private string name;
public Employee(Int32 age, string name)
{
this.age = age;
this.name = name;
}
}
在此示例中,this.age
指的是成员变量,而 age
指的是参数。同样,this.name
指的是成员变量,而 name
指的是参数。现在回到实例方法,实例方法是对象的方法,通常影响对象本身。一个 Employee
类可能会声明一个方法来增加 salary
字段
public class Employee {
private Int32 salary; // field
public PayManCash(Int32 increment)
{
salary += increment;
}
}
公共语言运行时在称为公共类型系统 (CTS) 的定义良好的类型系统范围内执行代码。CTS 是公共语言基础结构 (CLI) 规范的一部分,并构成了托管程序和运行时本身之间的接口。这意味着 C# 是一种强类型语言。这意味着您必须告诉编译器您将使用的对象的类型。因此,如果有人试图将 int32
作为 string
传递,编译器会捕获它并通知您该错误。这实际上使编译器错误很有帮助,因为错误和错误是在编译时而不是在运行时捕获的。因此,运行时错误不是一件好事。因此,虽然 C# 区分了用户定义类型和内置类型,但它也区分了值类型和引用类型。值类型在线性堆栈上作为字节序列存储,而引用类型存储在堆上。堆栈是一块充当抽象数据结构的内存。堆栈是为值类型预留的内存区域。堆栈的大小有限,值根据需要添加到堆栈中。值在销毁时从堆栈中“弹出”。堆是一个未分化的内存区域,用于创建用户定义类型(类)。当在堆上创建对象时,会返回一个引用,并且当不再有对该对象的活动引用时,内置的垃圾回收器会清除堆上的对象... 当您将参数传递给方法时,它们默认总是按值传递。更确切地说,会创建一个副本,并在方法中使用该副本。因此,如果您传递值类型,则会传递该对象的一个副本。如果您传递引用类型,则会传递对该对象的引用的一个副本。这是一个重要的区别。由于 CLR 默认假定所有方法参数都按值传递,因此当传递引用类型对象时,会将对该对象的引用(或指针)按值传递给方法。这意味着方法可以修改对象,并且调用者将看到更改。对于值类型实例,同样,会将该实例的副本传递给方法。这意味着方法会获得其自己的值类型的私有副本,并且调用者中的调用者和实例不会受到影响。现在回顾一下实例方法和静态方法与构造函数使用的区别。值类型(实例)构造函数的行为与引用类型(类)构造函数不同。因此,除了实例构造函数之外,CLR 还支持类型构造函数(也称为静态构造函数、类构造函数或类型初始化器)。
例如,假设您有一个名为 Dog
的对象,它通过引用传递。该狗可以被改变,因为存在一个引用。如果您将一个整数传递给一个方法,您不能在调用方法中改变 Int32
,因为它是一个副本。为了说明这一点,请查看下面显示的代码。我们创建一个方法来交换两个整数。此方法接受两个参数,left
和 right
。然后它创建一个临时变量,并将参数 left
中的值赋给该临时变量。然后它将 right
中的值赋给 left
,并将临时变量中的值赋给 right
,从而交换值。该方法在交换前后都显示 left
和 right
中的值
using System;
public class Program {
static void DoSwap(Int32 left, Int32 right)
{
Int32 temp;
Console.WriteLine("Swap before. left: {0}, right: {1}",left, right);
temp = left;
left = right;
right = temp;
Console.WriteLine("\nSwap after. left: {0}, right: {1}",left, right);
}
static void Main()
{
Int32 x = 5;
Int32 y = 7;
Console.WriteLine("Main before. x: {0}, y: {1}", x, y);
DoSwap(x, y);
Console.WriteLine("Main after. x: {0}, y: {1}", x, y);
}
}
编译此代码会产生
Main before. x: 5, y: 7
Swap before. left: 5, right: 7
Swap after. left: 7, right: 5
Main after. x: 5, y: 7
您可以看到 swap 方法显示值已交换,但值并未在 Main
中再次交换。这与 Int32
变量按值传递的理念一致。公共语言运行时允许您通过引用而不是按值传递参数。在 C# 语言中,您通过使用 out 和 ref 关键字来实现这一点。这两个关键字都告诉编译器发出元数据,指示此指定参数按引用传递,并且编译器使用此元数据生成代码以传递参数的地址而不是参数本身。从 CLR 的角度来看,out 和 ref 是相同的——也就是说,无论您使用哪个关键字,都会生成相同的元数据和 IL。看到上述代码中的问题,您希望通过在方法和方法调用中添加 ref 关键字,使 Int32
按引用传递
using System;
public class Program {
static void DoSwap( ref Int32 left, ref Int32 right)
{
Int32 temp;
Console.WriteLine("Swap before. left: {0}, right: {1}",left, right);
temp = left;
left = right;
right = temp;
Console.WriteLine("\nSwap after. left: {0}, right: {1}",left, right);
}
static void Main()
{
Int32 x = 5;
Int32 y = 7;
Console.WriteLine("Main before. x: {0}, y: {1}", x, y);
DoSwap( ref x, ref y);
Console.WriteLine("Main after. x: {0}, y: {1}", x, y);
}
}
通过添加 ref 关键字执行此代码会产生截然不同的结果:值在 Main
中被更改回来
Main before. x: 5, y: 7
Swap before. left: 5, right: 7
Swap after. left: 7, right: 5
Main after. x: 7, y: 5
属性
如前所述,类具有行为和状态。您使用方法对行为进行建模,并使用成员变量(有时称为字段)对状态进行建模。您通过属性提供对类状态的访问。成员变量应该是 private
。面向对象编程的标准之一是数据封装。数据封装意味着您的类型的字段永远不应该公开,因为很容易编写不正确使用字段、破坏对象状态的代码。这意味着您的字段应该是 private
。然后,为了允许您的类型的用户获取或设置状态信息,您为此目的公开方法。包装对字段访问的方法通常称为访问器方法。属性几乎可以向开发人员显示为方法,但对于您的类型的用户来说,属性似乎可以直接访问字段,而实际上他们被禁止更改对象的状态。属性语句有两部分:用于检索值的 get
部分,以及用于设置值的 set
部分。set
部分有一个隐式参数:value,它是正在设置的值。考虑这个例子
public class Employee {
private Int32 age; // private member variable
public Int32 Age // property – note capital A
{
get
{
return age; // can change later to compute
}
set
{
age = value; // use implicit parameter
}
}
}
不可否认,属性使类型的定义复杂化,但不得不编写更多代码的缺点应该抵消不管理对象状态所带来的问题。下面是一个旨在进一步解释此问题的示例
using System;
// Create an Employee class with two private member variables
public class Employee
{
private int baseLevel;
private string name;
// Provide a constructor that sets these two fields
public Employee(string name, int baseLevel)
{
this.name = name;
this.baseLevel = baseLevel;
}
// Create properties to match the two private member variables
public int BaseLevel
{
get
{
return baseLevel;
}
set
{
baseLevel = value;
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// You are now able to access the state of the Employee class through the
// properties you’ve created
// so now instantiate a couple of Employee objects in Main()
static void Main()
{
Employee fred = new Employee("Fred", 2);
Employee joe = new Employee("Joe", 5);
// You can access the state of the fred instance through the get properties
Console.WriteLine("{0}'s base: {1}",fred.Name,fred.BaseLevel);
joe.BaseLevel = 12;
// use get to see the new values
Console.WriteLine("{0}'s base: {1}",joe.Name,joe.BaseLevel);
}
}
参考文献
- CLR via C#,第二版,Jeffrey Richter 著
- Professional .NET Framework 2.0,Joe Duffy 著
- Jesse Liberty 的笔记
历史
- 2009 年 10 月 31 日:首次发布