C# 属性
在 C# 中,属性(Property)是一种特殊的成员,它允许我们通过访问器(Accessor)来访问一个类的私有字段(Private Field)。使用属性,我们可以将一个字段的值读取或者设置,而不用暴露字段本身。
- 属性使得访问类成员更加简洁和安全。
- 这里的字段指的是直接在类当中定义的变量,在Python中会将这些变量称为实例属性,但是注意C#中是不同的:
- 字段定义时通常采用
private
访问修饰符,因此在类的外部通常是没法直接访问的,自然通过实例对象也没法直接获取字段值。 - 而属性通常采用
public
访问修饰符,因此通过实例对象可以直接访问属性,属性内部包含的访问器可以让我们获取和修改字段值。
- 字段定义时通常采用
1、定义属性
属性的定义包含以下几个部分:
- 访问修饰符(Access Modifier):用来控制属性的访问级别,和字段的访问修饰符类似。
- 数据类型(Data Type):属性的返回值类型,可以是任何有效的 C# 数据类型。
- 属性名(Property Name):属性的名称,遵循 C# 命名规则。
- 访问器(Accessor):属性的读取和写入方法,可以是
get
和set
访问器。
属性的定义语法如下:
[Access Modifier] [Data Type] [Property Name]
{
get {
/* get 访问器代码 */ }
set {
/* set 访问器代码 */ }
}
下面是一个例子,定义了一个 Person
类,包含 Name
和 Age
两个属性:
class Person
{
private string name;
private int age;
public string Name
{
get {
return name; }
set {
name = value; }
}
public int Age
{
get {
return age; }
set {
age = value; }
}
}
在这个例子中,我们使用了 get
和 set
访问器来读取和写入私有字段 name
和 age
的值。注意,访问器中的代码可以包含任何有效的 C# 语句,包括条件语句、循环语句等等。
在使用属性时:
- 通过
实例名.属性名
会自动调用属性的get
方法来获取属性的值; - 如果对
实例名.属性名
进行赋值,则会自动调用set
方法来设置属性的值。
以下是一个示例代码,展示了如何使用 C# 中的属性:
public class Person
{
private string _name;
private int _age;
public string Name
{
get {
return _name; }
set {
_name = value; }
}
public int Age
{
get {
return _age; }
set {
_age = value; }
}
}
public class Program
{
static void Main(string[] args)
{
var person = new Person();
person.Name = "Alice"; // 设置 Name 属性的值,调用 set 方法
person.Age = 25; // 设置 Age 属性的值,调用 set 方法
Console.WriteLine("Name: " + person.Name); // 获取 Name 属性的值,调用 get 方法
Console.WriteLine("Age: " + person.Age); // 获取 Age 属性的值,调用 get 方法
}
}
2、自动属性
在 C# 3.0 中,引入了自动属性(Auto-Implemented Properties),使得属性的定义更加简洁。使用自动属性,我们可以将属性的定义简化为一行代码:
[Access Modifier] [Data Type] [Property Name] {
get; set; }
下面是使用自动属性的例子:
class Person
{
public string Name {
get; set; }
public int Age {
get; set; }
}
在这个例子中,我们省略了访问器的具体实现,使用了自动属性来定义 Name
和 Age
两个属性。编译器会自动为这两个属性生成一个私有字段,并且为它们生成默认的 get { return <私有字段名>; }
和 set { <私有字段名> = value; }
访问器。
3、抽象属性
在 C# 中,抽象类可拥有抽象属性,这些属性应在派生类中被实现。需要注意自动属性和抽象属性是两种不同类型的属性。它们之间有以下几个区别:
- 实现方式:自动属性是通过编译器自动为属性生成
get
和set
方法的,而抽象属性需要在抽象类或接口中定义抽象get
和set
方法(需要使用abstract
关键字)。 - 实现细节:自动属性的
get
和set
方法的实现是由编译器生成的,而抽象属性的get
和set
方法需要在子类中进行实现。 - 使用场景:自动属性适用于简单的属性定义,而抽象属性通常用于定义接口或抽象类中的属性,以便在子类中进行实现。
以下是一个示例代码,展示了自动属性和抽象属性的使用:
// 自动属性示例
public class Person
{
public string Name {
get; set; }
public int Age {
get; set; }
}
// 抽象属性示例
public abstract class Animal
{
public abstract string Name {
get; set; }
public abstract int Age {
get; set; }
}
public class Dog : Animal
{
public override string Name {
get; set; } // 用自动属性实现了抽象属性
public override int Age {
get; set; } // 用自动属性实现了抽象属性
}
在上面的示例代码中,我们定义了一个 Person
类和一个抽象类 Animal
。Person
类中使用了自动属性来定义 Name
和 Age
属性,而 Animal
抽象类中使用抽象属性来定义 Name
和 Age
属性。在 Dog
类中,我们实现了抽象属性,以便子类对象使用。
4、只读属性和初始化器
有时候,我们希望定义一个只读的属性,即只能在对象的构造函数中初始化,不能在其他地方进行修改。为了实现这个目标,我们可以使用只读属性和初始化器。
- 只读属性的定义方式和普通属性一样,只不过将 set 访问器删除:
class Person
{
public string Name {
get; }
public int Age {
get; }
// 构造函数,进行属性(对应私有字段)的初始化
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
在这个例子中,我们定义了 Name
和 Age
两个只读属性,它们只能在构造函数中进行初始化。一旦对象被创建,它们的值就不能再进行修改。
- 除了只读属性,还可以使用对象初始化器(Object Initializer)来为对象的属性赋初值。对象初始化器的语法如下:
new [Type]
{
[Property1 = Value1],
[Property2 = Value2],
// ...
}
简单来说,就是在 new 创建对象的时候,通过添加一组“键值对”为每个属性(对应的私有字段)赋初值。下面是一个例子,使用对象初始化器为 Person
对象的 Name
和 Age
两个属性赋初值:
var person = new Person
{
Name = "Tom",
Age = 20
};
C# this关键字
在C#中,this
关键字表示当前对象的实例,类似于Python中的 self
关键字,但是二者的使用有一定区别。this
关键字可以用于以下几种情况:
- 区分字段、局部变量和方法参数:
在方法或构造函数中,如果存在一个局部变量或方法参数与类的字段同名,则可以使用 this
关键字来区分它们,如下所示:
class MyClass
{
private int _value;
public void SetValue(int value)
{
this._value = value; // 使用this关键字来区分字段和方法参数
}
}
在上面的示例中,我们使用 this._value
来引用类的私有字段 _value
,以区分方法参数 value
。
- 在构造函数中调用其他构造函数:
在一个类的多个构造函数中,如果需要共享一些相同的初始化逻辑,可以使用 this
关键字来调用其他构造函数,如下所示:
class MyClass
{
private int _value;
public MyClass() : this(0) // 调用另一个构造函数来初始化_value
{
}
public MyClass(int value)
{
this._value = value;
}
}
在上面的示例中,我们定义了两个构造函数,其中一个调用了另一个构造函数来初始化类的字段。这样做可以避免重复代码。
- 对当前对象的引用:
在C#中,当前对象的引用指的是关键字 this
所代表的对象。可以使用关键字 this
来访问当前对象的属性、方法或索引器。例如,可以使用 this.<propertyName>
来访问当前对象的属性,或者使用 this.<methodName>()
来调用当前对象的方法。
需要注意的是,当前对象的引用只在非静态方法和构造函数中才有意义,因为静态方法不依赖于任何对象实例。
此外,当前对象的引用也可以作为参数传递给其他方法,以便在方法内部使用该对象。
需要注意的是,在C#中对象作为参数传递时,默认情况下是按引用传递,而不是按值传递
以下是一个使用 this
关键字引用当前对象的示例代码:
public class Person
{
private string name;
private int age;
public Person(string name, int age)
{
this.name = name; // 使用 this 关键字引用当前对象的 name 字段
this.age = age; // 使用 this 关键字引用当前对象的 age 字段
}
public void PrintDetails()
{
Console.WriteLine("Name: {0}, Age: {1}", this.name, this.age); // 使用 this 关键字引用当前对象的 name 和 age 字段
}
}
public class Program
{
static void Main(string[] args)
{
Person person = new Person("John", 30);
person.PrintDetails(); // 输出 "Name: John, Age: 30"
}
}
通过上面的代码我们可以发现,访问一个类的私有字段,除了使用属性中的访问器实现,还可以通过在公开的类方法中使用 this
关键字引用当前对象的字段来实现。
C# 索引器
在 C# 中,索引器(Indexer)是一个特殊的属性(Property),它允许对象像数组一样进行索引操作。在C#中,索引器使用 this
关键字来定义,并且可以像数组一样使用方括号[]
来访问元素。
1、索引器的定义
索引器的声明在某种程度上类似于属性(property),我们可以使用 get
和 set
访问器来定义索引器。但是,属性返回或设置一个特定的数据成员,而索引器返回或设置对象实例的一个特定值。
换句话说,它把实例数据分为更小的部分,并索引每个部分,获取或设置每个部分。
- 定义一个属性(property)包括提供属性名称。
- 索引器定义的时候不带有名称,但带有
this
关键字,它指向对象实例。
<access-modifier> <return type> this [index]
{
// get 访问器
get
{
// 返回 index 指定的值
}
// set 访问器
set
{
// 设置 index 指定的值
}
}
其中:
access-modifier
表示访问修饰符,例如public、private、protected等。return type
表示索引器的返回类型。index
表示索引器的参数,可以是一个或多个。参数类型可以是整数、字符串等任何类型。get
和set
是访问器,用于获取和设置索引器的值。
下面是一个使用C#索引器的示例代码:
class MyClass
{
private string[] _data = new string[10];
public string this[int index]
{
get {
return _data[index]; } // 获取当前类的实例对象的索引结果,访问私有字符串数组的索引结果
set {
_data[index] = value; } // 为当前类的实例对象的索引位置赋值,访问私有字符串数组的索引位置
}
}
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass[0] = "hello";
myClass[1] = "world";
Console.WriteLine(myClass[0]); // 输出:hello
Console.WriteLine(myClass[1]); // 输出:world
}
}
在上面的示例代码中,我们定义了一个名为MyClass
的类,并在该类中定义了一个 this[int index]
索引器。该索引器接受一个整数参数 index
,并使用一个私有成员变量 _data
来存储数据。在 get
访问器中,我们返回 _data[index]
的值;在 set
访问器中,我们设置 _data[index]
的值。
在Main方法中,我们创建了一个 MyClass
类的实例对象 myClass
,并使用方括号 []
来访问该对象的索引器。我们将字符串"hello"和"world"存储在了索引为0和1的位置上,并使用 Console.WriteLine
方法来输出它们的值。
2、重载索引器
索引器(Indexer)可被重载。下面的例子展示了通过不同的 index
参数的数据类型,实现索引器重载(与函数重载相同):
using System;
namespace IndexerApplication
{
class IndexedNames
{
private string[] namelist = new string[size]; // 存储字符串的数组
static public int size = 10; // 数组的大小为10
public IndexedNames()
{
// 构造函数,初始化数组的元素为 "N. A."
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
}
}
// 读写索引器,用于按照整数索引数组元素
public string this[int index]
{
get
{
string tmp;
if (index >= 0 && index <= size - 1)
{
tmp = namelist[index];
}
else
{
tmp = "";
}
return tmp;
}
set
{
if (index >= 0 && index <= size - 1)
{
namelist[index] = value;
}
}
}
// 只读索引器,用于按照字符串索引数组元素
public int this[string name]
{
get
{
int index = 0;
while (index < size)
{
if (namelist[index] == name)
{
return index;
}
index++;
}
return index;
}
}
static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
// 使用第一个索引器给数组元素赋值
names[0] = "Zara";
names[1] = "Riz";
names[2] = "Nuha";
names[3] = "Asif";
names[4] = "Davinder";
names[5] = "Sunil";
names[6] = "Rubic";
// 使用第一个索引器获取数组元素的值
for (int i = 0; i < IndexedNames.size; i++)
{
Console.WriteLine(names[i]);
}
// 使用第二个索引器获取数组元素的值
Console.WriteLine(names["Nuha"]);
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.
2
3、多参数索引器
索引器声明的时候也可带有多个参数,且每个参数可以是不同的类型,可以根据需要返回不同类型的值。以下是一个示例,展示了使用两个参数(整数和字符串)的索引器,并返回整数类型和字符串类型的值:
using System;
namespace MultiIndexerApplication
{
class MultiIndexer
{
// 定义两个数组,一个用于存储整数,一个用于存储字符串
private int[] intArray = new int[10];
private string[] stringArray = new string[10];
// 定义带有两个参数的索引器,一个整数类型的索引,一个字符串类型的索引
public int this[int index, string type]
{
// 索引器的 get 方法,根据类型返回对应的值
get
{
if (type == "int")
{
return intArray[index]; // 返回整数
}
else if (type == "string")
{
return int.Parse(stringArray[index]); // 字符串转换为整数后返回
}
else
{
return 0;
}
}
// 索引器的 set 方法,根据类型设置对应的值
set
{
if (type == "int")
{
intArray[index] = value; // 给索引结果赋值整数
}
else if (type == "string")
{
stringArray[index] = value.ToString(); // 将整数转换成字符串,然后赋值给索引结果
}
}
}
static void Main(string[] args)
{
// 创建 MultiIndexer 类的实例
MultiIndexer multiIndexer = new MultiIndexer();
// 设置数组元素的值
multiIndexer[0, "int"] = 1;
multiIndexer[0, "string"] = "2";
// 使用索引器获取数组元素的值,并输出
Console.WriteLine(multiIndexer[0, "int"]); // 输出 1
Console.WriteLine(multiIndexer[0, "string"]); // 输出 2
}
}
}
上面的代码中,索引器的两个参数 int index
和 string type
分别用来指定下标位置和数组类型。当然我们也可以使用多参数来实现多维数组的索引,这里不再举例。