常量、字段、方法、属性和索引器(C#)

时间:2021-06-10 14:55:49

常量(Constants)

常量是在编译时已知并在程序的生存期内不发生更改的不可变值。 常量使用 const 修饰符进行声明。 只有 C# 内置类型(System.Object 除外)可以声明为 const。用户定义的类型(包括类、结构和数组)不能为 const。 请使用 readonly 修饰符创建在运行时初始化一次即不可再更改的类、结构或数组。
注:内置类型表如下

常量、字段、方法、属性和索引器(C#)

常量必须在声明时初始化。例如:
C#:

class Calendar1 {
    public const int months = 12;
}

在此示例中,常量 months 始终为 12,不可更改,即使是该类自身也不能更改它。 实际上,当编译器遇到 C# 源代码(例如 months)中的常量修饰符时,将直接把文本值替换到它生成的中间语言 (IL) 代码中。 因为在运行时没有与常量关联的变量地址,所以 const 字段不能通过引用传递,并且不能在表达式中作为左值出现。

字段(fields )

“字段”是直接在类或结构中声明的任何类型的变量。 字段是其包含类型的“成员”。

类或结构可以拥有实例字段(instance fields)静态字段( static fields ),或同时拥有两者。 实例字段特定于类型的实例。 如果您拥有类 T 和实例字段 F,可以创建类型 T 的两个对象,并修改每个对象中 F 的值,这不影响另一对象中的该值。 相比之下,静态字段属于类本身,在该类的所有实例*享。 从实例 A 所做的更改将立刻呈现在实例 B 和 C 上(如果它们访问该字段)。

通常应仅为具有私有或受保护可访问性的变量使用字段。 您的类向客户端代码公开的数据应通过方法、属性和索引器提供。 通过使用这些构造间接访问内部字段,可以针对无效的输入值提供防护。 存储由公共属性公开的数据的私有字段称为“后备存储”或“支持字段”。

  • 在类块中通过指定字段的访问级别,然后指定字段的类型,再指定字段的名称来声明这些字段。 例如:
    C#:
public class CalendarEntry
{
    // private field
    private DateTime date;

    // public field (Generally not recommended.)
    public string day;
    ...
}
  • 若要访问对象中的字段,请在对象名称后面添加一个句点,然后添加该字段的名称,比如 objectname.fieldname。 例如:
    C#:
CalendarEntry birthday = new CalendarEntry();
birthday.day = "Saturday";
  • 声明字段时可以使用赋值运算符为字段指定一个初始值。 例如,若要自动将 “Monday” 赋给 day 字段,需要声明 day,如下例所示:
    C#:
public class CalendarDateWithInitialization
{
    public string day = "Monday";
    //...
}
  • 字段可标记为 public、private、protected、internal 或 protected internal。 这些访问修饰符定义类的使用者访问字段的方式。
    注:访问修饰符

常量、字段、方法、属性和索引器(C#)

  • 可以选择将字段声明为 static。 这使得调用方在任何时候都能使用字段,即使类没有任何实例。

  • 可以将字段声明为 readonly。 只读字段只能在初始化期间或在构造函数中赋值。 static readonly 字段非常类似于常数,只不过 C# 编译器不能在编译时访问静态只读字段的值,而只能在运行时访问。

注:变量与字段的区别与联系

public class Test
{
    // field在这里是一个字段变量
    public int field =0;
    // 静态字段,
    public static int filed2 =1;

    public void TestMethod()
    {
        // 此时a是一个变量,并且是一个局部变量,a就不是字段了
        // 而对于field 和field2我们最好认为其是一个字段,但是它也是变量,我们一般叫法为 “字段变量”
        int a =2;
        Console.Write(a);
    }
}

方法(method)

方法是包含一系列语句的代码块。 程序通过调用该方法并指定任何所需的方法参数使语句得以执行。 在 C# 中,每个执行的指令均在方法的上下文中执行。 Main 方法是每个 C# 应用程序的入口点,并在启动程序时由公共语言运行时 (CLR) 调用。

方法签名

  • 通过指定访问级别(如 public 或 private)、可选修饰符(如 abstract 或 sealed)、返回值、方法的名称以及任何方法参数,在类或结构中声明方法。 这些部件一起构成方法的签名。
  • 方法参数在括号内,并且用逗号分隔。 空括号指示方法不需要任何参数。

注:出于方法重载的目的,方法的返回类型不是方法签名的一部分。 但是在确定委托和它所指向的方法之间的兼容性时,它是方法签名的一部分。


补充:方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。比如:

   public void Func(){}
   public void Func(int a){}
   public void Func(int a,int b){}
   ···
   Func(a);//会调用public void Func(int a)这个版本

具体规范:

  • 方法名一定要相同。
  • 方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体。(如果参数个数不同,就不管它的参数类型了。如果参数个数相同,那么参数的类型或者参数的顺序必须不同。)
  • 方法的返回类型、修饰符可以相同,也可不同。即在C#中不能只根据返回值的类型不同来区分不同的重载函数。

方法访问

调用对象上的方法就像访问字段。 在对象名之后添加一个句点、方法名和括号。 参数列在括号里,并且用逗号分隔。

方法参数与参数

该方法定义指定任何所需参数的名称和类型。调用代码调用该方法时,它为每个参数提供了称为参数的具体值。 参数必须与参数类型兼容,但调用代码中使用的参数名(如果有)不需要与方法中定义的参数名相同。

按引用传递与按值传递

默认情况下,值类型传递给方法时,传递的是副本而不是对象本身。 因此,对参数的更改不会影响调用方法中的原始副本。可以使用 ref 关键字按引用传递值类型。

引用类型的对象传递到方法中时,将传递对对象的引用。 也就是说,该方法接收的不是对象本身,而是指示该对象位置的参数。 如果通过使用此引用更改对象的成员,即使是按值传递该对象,此更改也会反映在调用方法的参数中。

通过使用 class 关键字创建引用类型,如以下示例所示。

public class SampleRefType
{
    public int value;
}

现在,如果将基于此类型的对象传递到方法,则将传递对对象的引用。 下面的示例将 SampleRefType 类型的对象传递到 ModifyObject 方法。

public static void TestRefType()
{
    SampleRefType rt = new SampleRefType();
    rt.value = 44;
    ModifyObject(rt);
    Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
    obj.value = 33;
}

该示例执行的内容实质上与先前示例相同,均按值将参数传递到方法。 但是因为使用了引用类型,结果有所不同。 ModifyObject 中所做的对形参 obj 的 value 字段的修改,也会更改 TestRefType 方法中实参 rt 的 value 字段。 TestRefType 方法显示 33 作为输出。

返回值

方法可以将值返回到调用方。如果列在方法名之前的返回类型不是 void,则该方法可通过使用 return 关键字返回值。带 return 关键字,后跟与返回类型匹配的值的语句将该值返回到方法调用方。return 关键字还会停止执行该方法。如果返回类型为 void,没有值的 return 语句仍可用于停止执行该方法。 没有 return 关键字,当方法到达代码块结尾时,将停止执行。具有非空的返回类型的方法都需要使用 return 关键字来返回值。例如,这两种方法都使用 return 关键字来返回整数:

class SimpleMath
{
    public int AddTwoNumbers(int number1, int number2)
    {
        return number1 + number2;
    }

    public int SquareANumber(int number)
    {
        return number * number;
    }
}

若要使用从方法返回的值,调用方法可以在相同类型的值足够的地方使用该方法调用本身。 也可以将返回值分配给变量。 例如,以下两个代码示例实现了相同的目标:

int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));
// The result is 9.
Console.WriteLine(result);

属性(property)

属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。 属性可用作公共数据成员,但它们实际上是称为“访问器”的特殊方法。 这使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。

属性结合了字段和方法的多个方面。 对于对象的用户,属性显示为字段,访问该属性需要相同的语法。 对于类的实现者,属性是一个或两个代码块,表示一个 get 访问器和/或一个 set 访问器。 当读取属性时,执行 get 访问器的代码块;当向属性分配一个新值时,执行 set 访问器的代码块。 不具有 set 访问器的属性被视为只读属性。 不具有 get 访问器的属性被视为只写属性。 同时具有这两个访问器的属性是读写属性。

与字段不同,属性不作为变量来分类。 因此,不能将属性作为 ref(C# 参考)参数或 out(C# 参考)参数传递。

属性具有多种用法:它们可在允许更改前验证数据;它们可透明地公开某个类上的数据,该类的数据实际上是从其他源(例如数据库)检索到的;当数据被更改时,它们可采取行动,例如引发事件或更改其他字段的值。

属性在类块中是按以下方式来声明的:指定字段的访问级别,接下来指定属性的类型和名称,然后跟上声明 get 访问器和/或 set 访问器的代码块。 例如:

public class Date
{
    private int month = 7;  // Backing store

    public int Month
    {
        get
        {
            return month;
        }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}

在此示例中,Month 是作为属性声明的,这样 set 访问器可确保 Month 值设置为 1 和 12 之间。 Month 属性使用私有字段来跟踪该实际值。 属性的数据的真实位置经常称为属性的“后备存储”。属性使用作为后备存储的私有字段是很常见的。 将字段标记为私有可确保该字段只能通过调用属性来更改。

get 访问器

get 访问器体与方法体相似。 它必须返回属性类型的值。 执行 get 访问器相当于读取字段的值。

以下是返回私有字段 name 的值的 get 访问器:

class Person {
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
    }
}

当引用属性时,除非该属性为赋值目标,否则将调用 get 访问器以读取该属性的值。 例如:

Person person = new Person();
//...

System.Console.Write(person.Name);  // the get accessor is invoked here

get 访问器必须以 return 或 throw 语句终止,并且控制权不能离开访问器体。

通过使用 get 访问器更改对象的状态不是一种好的编程风格。 例如,以下访问器在每次访问 number 字段时都会产生更改对象状态的副作用。

private int number;
public int Number
{
    get
    {
        return number++;   // Don't do this
    }
}

get 访问器可用于返回字段值,或用于计算并返回字段值。 例如:

class Employee
{
    private string name;
    public string Name
    {
        get
        {
            return name != null ? name : "NA";
        }
    }
}

在上一个代码段中,如果不对 Name 属性赋值,它将返回值 NA。

set 访问器

set 访问器类似于返回类型为 void 的方法。 它使用称为 value 的隐式参数,此参数的类型是属性的类型。 在下面的示例中,将 set 访问器添加到 Name 属性:

class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

当对属性赋值时,用提供新值的参数调用 set 访问器。 例如:

Person person = new Person();
person.Name = "Joe"; // the set accessor is invoked here 

System.Console.Write(person.Name); // the get accessor is invoked here

在 set 访问器中,对局部变量声明使用隐式参数名称 value 是错误的。

备注

  • 可将属性标记为 public、private、protected、internal 或 protected internal。 这些访问修饰符定义类的用户如何才能访问属性。 同一属性的 get 和 set 访问器可能具有不同的访问修饰符。 例如,get 可能是 public 以允许来自类型外的只读访问;set 可能是 private 或 protected。 有关更多信息,请参见 访问修饰符(C# 编程指南)
  • 可以使用 static 关键字将属性声明为静态属性。 这使得调用方随时可使用该属性,即使不存在类的实例。 有关更多信息,请参见 静态类和静态类成员(C# 编程指南)
  • 可以使用 virtual 关键字将属性标记为虚属性。 这样,派生类就可以通过使用 override 关键字来重写事件行为。 有关这些选项的更多信息,请参见 继承(C# 编程指南)
  • 重写虚属性的属性还可以是 sealed 的,这表示它对派生类不再是虚拟的。 最后,可以将属性声明为 abstract。 这意味着类中没有任何实现,派生类必须编写自己的实现。 有关这些选项的更多信息,请参见 抽象类、密封类及类成员(C# 编程指南)
  • 对 static 属性的访问器使用 virtual、abstract或 override修饰符是错误的。

索引器(Indexers)

  • 索引器允许类或结构的实例就像数组一样进行索引。 索引器类似于属性,不同之处在于它们的取值函数采用参数。
  • 使用索引器可以用类似于数组的方式为对象建立索引。
  • get 取值函数返回值。 set 取值函数分配值。
  • this 关键字用于定义索引器。
  • value 关键字用于定义由 set 索引器分配的值。
  • 索引器不必根据整数值进行索引;由你决定如何定义 特定的查找机制。
  • 索引器可被重载。
  • 索引器可以有多个形参,例如当访问二维数组时。

在下面的示例中,定义了一个泛型类,并为其提供了简单的 get 和 set 取值函数方法(作为分配和检索值的方法)。 Program 类创建了此类的一个实例,用于存储字符串。

class SampleCollection<T>
{
    // Declare an array to store the data elements.
    private T[] arr = new T[100];

    // Define the indexer, which will allow client code
    // to use [] notation on the class instance itself.
    // (See line 2 of code in Main below.) 
    public T this[int i]
    {
        get
        {
            // This indexer is very simple, and just returns or sets
            // the corresponding element from the internal array.
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer.
class Program
{
    static void Main(string[] args)
    {
        // Declare an instance of the SampleCollection type.
        SampleCollection<string> stringCollection = new SampleCollection<string>();

        // Use [] notation on the type.
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}
// Output:
// Hello, World.

参考资料

  1. 常量(C# 编程指南):https://msdn.microsoft.com/zh-cn/library/ms173119.aspx
  2. 字段(C# 编程指南):https://msdn.microsoft.com/zh-cn/library/ms173118.aspx
  3. 方法(C# 编程指南):https://msdn.microsoft.com/zh-cn/library/ms173114.aspx
  4. 使用属性(C# 编程指南):https://msdn.microsoft.com/zh-cn/library/w86s7x04.aspx
  5. 索引器(C# 编程指南):https://msdn.microsoft.com/zh-cn/library/6x16t2tx.aspx#bkmk_relatedsections