C# (const和readonly、静态类、函数、成员、out和ref)

Posted by moloach on 2017-08-02

const 和 readonly 的区别

  1. const 一般用于编译时期常数
  2. ReadOnly 一般用于执行时期常数

const仅可用于数字和字符串,readOnly可以为任意形态

1
2
3
4
5
public const DateTime CreateDatetime1 = new DateTIme(2000,1,1,0,0,1);
//it will report a error.

public static readonly DateTime CreateDatetime1 = new DateTIme(2000,1,1,0,0,1);
//OK.

const 能用在方法中,readonly不行

const 是在编译时产生的,readonly是在运行时产生的

引用一个readonly常数是引用其变数,而引用const是引用其值,会影响执行时的相容性。

reference: https://dotblogs.com.tw/yc421206/archive/2011/06/06/27232.aspx

静态类和静态类成员

静态类和非静态类没什么区别,只是静态类不能实例化(无法使用new关键字创建类类型变量)。

因为不存在任何实例,因此可以使用类名本身访问静态类成员。

如同System.Math包含的数学运算。

1
2
3
4
5
6
7
8

using System.Math;

double dub = -3.14;

Math.Abs(dub);
Math.Floor(dub);
Math.Round(Math.Abs(dub));

与所有类类型的情况一样,静态类的类型信息在引用该类的程序加载时,由 .NET Framework 公共语言运行时 (CLR) 加载.

静态类主要功能

  • 只包含静态成员
  • 无法实例化
  • 会进行密封
  • 不能包含实例构造函数

注意事项和编写建议

  1. 创建静态类基本上于只创建静态成员和私有构造函数的的类相同。
    • 只包含静态成员
    • 私有构造函数可以防止类进行实例化
  2. 优点
    • 使用静态类的优点是编译器可以进行检查,以确保不会意外地添加任何实例成员。 编译器可保证无法创建此类的实例。
  3. 静态类会进行密封,因此不能继承
    • 除了Object之外不能继承自任何类
  4. 构造函数
    • 不能包含实例构造函数
    • 可包含静态构造函数
    • 如果类包含需要进行重要初始化的静态成员,则非静态类还应定义静态构造函数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      public static class TemperatureConverter
      {
      public static double CelsiusToFahrenheit(string temperatureCelsius)
      {
      // Convert argument to double for calculations.
      double celsius = Double.Parse(temperatureCelsius);

      // Convert Celsius to Fahrenheit.
      double fahrenheit = (celsius * 9 / 5) + 32;

      return fahrenheit;
      }

      public static double FahrenheitToCelsius(string temperatureFahrenheit)
      {
      // Convert argument to double for calculations.
      double fahrenheit = Double.Parse(temperatureFahrenheit);

      // Convert Fahrenheit to Celsius.
      double celsius = (fahrenheit - 32) * 5 / 9;

      return celsius;
      }
      }

      class TestTemperatureConverter
      {
      static void Main()
      {
      Console.WriteLine("Please select the convertor direction");
      Console.WriteLine("1. From Celsius to Fahrenheit.");
      Console.WriteLine("2. From Fahrenheit to Celsius.");
      Console.Write(":");

      string selection = Console.ReadLine();
      double F, C = 0;

      switch (selection)
      {
      case "1":
      Console.Write("Please enter the Celsius temperature: ");
      F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
      Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
      break;

      case "2":
      Console.Write("Please enter the Fahrenheit temperature: ");
      C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());
      Console.WriteLine("Temperature in Celsius: {0:F2}", C);
      break;

      default:
      Console.WriteLine("Please select a convertor.");
      break;
      }

      // Keep the console window open in debug mode.
      Console.WriteLine("Press any key to exit.");
      Console.ReadKey();
      }
      }
      /* Example Output:
      Please select the convertor direction
      1. From Celsius to Fahrenheit.
      2. From Fahrenheit to Celsius.
      :2
      Please enter the Fahrenheit temperature: 20
      Temperature in Celsius: -6.67
      Press any key to exit.
      */

静态成员

非静态类可以包含静态方法、字段、属性或事件。即使未创建类的任何实例,也可对类调用静态成员。

静态成员始终按类名(而不是实例名称)进行访问

静态成员只有一个副本存在(与创建的类的实例数有关)。

静态方法和属性无法在其包含类型中访问非静态字段和事件,它们无法访问任何对象的实例变量,除非在方法参数中显式传递它

更典型的做法是声明具有一些静态成员的非静态类(而不是将整个类都声明为静态)。

静态字段用途

静态字段的两个常见用途是保留已实例化的对象数的计数,或是存储必须在所有实例间共享的值。

静态方法可以进行重载,但不能进行替代,因为它们属于类,而不属于类的任何实例。

注意事项

  • 不能声明为 static const
  • const属于类型
  • C#不支持静态局部变量(在方法范围中声明的变量)
  • 可在成员的返回类型之前使用 static 关键字声明静态类成员
  • 在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。
  • 访问静态类成员,请使用类的名称(而不是变量名称)指定成员的位置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Automobile
    {
    public static int NumberOfWheels = 4;
    public static int SizeOfGasTank
    {
    get
    {
    return 15;
    }
    }
    public static void Drive() { }
    public static event EventType RunOutOfGas;

    // Other non-static fields and properties...

    Automobile.Dirve();
    int i = Automobile.NumberOfWheels;
    }

reference:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members

params关键字

指定采用数目可变的参数的方法参数。

可以发送参数声明中所指定类型的逗号分隔的参数列表或指定类型的参数数组。

还可以不发送参数。

如果未发送任何参数,则 params 列表的长度为零。

在方法声明中的 params 关键字之后不允许有任何其他参数,并且在方法声明中只允许有一个 params 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MyClass{
public static void User(params int[] list){
for (int i = 0; i<list.length;i++){
Console.Write(list[i] + " ");
}
Console.WirteLine();
}

public static void UserParams(params object[] list){
for (int i = 0; i< list.length;i++){
Console.Wirte(list[i]+ " ");
}
}

static void Main(string[] args){
// You can send a comma-separated list of arguments of the
// specified type.
User(1,3,4,6);
UserParams(1,"ret",'a');

// A params parameter accepts zero or more arguments.
// The following calling statement displays only a blank line.
UseParams2();

// An array argument can be passed, as long as the array
// type matches the parameter type of the method being called.
int[] myIntArray = { 5, 6, 7, 8, 9 };
UseParams(myIntArray);

object[] myObjArray = { 2, 'b', "test", "again" };
UseParams2(myObjArray);

// The following call causes a compiler error because the object
// array cannot be converted into an integer array.
//UseParams(myObjArray);

// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
UseParams2(myIntArray);
}
}

ref(参数)

ref 关键字通过引用(而非值)传递参数。

通过引用传递的效果是,对所调用方法中的参数进行的任何更改都反映在调用方法中。

例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会将对象替换为 ref 参数引用的对象,然后调用方的本地变量或数组元素将开始引用新对象。

note

不要混淆通过引用传递的概念与引用类型的概念。 这两种概念是不同的。 无论方法参数是值类型还是引用类型,均可由 ref 修改。 当通过引用传递时,不会对值类型装箱。

如果要使用ref参数,方法定义和调用方法均必须显式使用ref关键字

传递到 ref 形参的实参必须先经过初始化,然后才能传递。 这与 out 形参不同,在传递之前,不需要显式初始化该形参的实参。

类的成员不能具有仅在 ref 和 out 方面不同的签名。 如果类型的两个成员之间的唯一区别在于其中一个具有 ref 参数,而另一个具有 out 参数,则会发生编译错误。 例如,以下代码将不会编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class RefExample{
static void Method(ref int i){
i = i + 4;
}

static void Main(){
int val = 1;
Method(ref val);
Console.WriteLine(val);
//Output 45;
}
}


class CS0663_Example{
//Compiler error CS0633:"can't define overloading "
//methods that differ only on out and out."

public void SampleMethod(out int i){}
public void SampleMethod(ref int i){}
}

//当一个方法具有 ref 或 out 参数,另一个方法具有值参数时,则可以完成重载

class REfOverloadExample{
public void SampleMethod(int i){};
public void SampleMethod(ref int i){};
}

注意事项

  • 在其他要求签名匹配的情况下(如隐藏或重写),ref 和 out 是签名的一部分,相互之间不匹配。
  • 不能讲ref 或者 out关键字用于以下几种方法。
    • 异步方法。通过使用async 修饰符修饰
    • 迭代器方法。 包括 yield return 或者yield break
  • 传递数组。
    • reference 使用out 和 ref 传递数组

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class RefExample{
static void Main(){
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);

// Pass the product instance to ChangeByReference.
ChangeByReference(ref item);
System.Console.WriteLine("Back in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
static void ChangeByReference(ref Product itemRef)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 99999);

// You can change the value of one of the properties of
// itemRef. The change happens to item in Main as well.
itemRef.ItemID = 12345;
}
}

class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}

public string ItemName { get; set; }
public int ItemID { get; set; }
}
// Output:
// Original values in Main. Name: Fasteners, ID: 54321
//
// Back in Main. Name: Stapler, ID: 12345

out 关键字

out使用引用传递参数。和ref关键字相似,只是ref要初始化变量。方法定义和方法使用都要使用out。

out 关键字也可以与泛型类型参数结合使用,以指定该类型参数是协变参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class OutExample
{
static void Method(out int i)
{
i = 44;
}

static void Main()
{
int value;
Method(out value);
Console.WriteLine(value); // value is now 44
}
}

其他功能和ref差不多

调用具有out 参数的方法

reference :https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/out-parameter-modifier#calling-a-method-with-an-out-argument