实现类方法和参数
方法是包含一系列语句的代码块。 程序通过调用该方法并指定任何必需的方法参数来执行语句。 在 C# 中,每个执行的指令都在方法的上下文中执行。
方法签名
方法通过指定在类、记录或结构中声明:
- 可选访问级别,例如
public或private。 默认值为private。 - 可选修饰符,如
abstract或sealed。 - 如果方法没有,则返回值或
void。 - 方法名称。
- 任何方法参数。 方法参数括在括号中,用逗号分隔。 空括号指示该方法不需要任何参数。
这些部分协同工作以形成方法签名。
重要
方法的返回类型不是方法重载的签名的一部分。 但是,确定委托与它指向的方法之间的兼容性时,它是方法签名的一部分。
以下示例定义一个名为 Motorcycle 的类,该类包含五种方法:
namespace MotorCycleExample
{
abstract class Motorcycle
{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }
// Only derived classes can call this.
protected void AddGas(int gallons) { /* Method statements here */ }
// Derived classes can override the base class implementation.
public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }
// Derived classes can override the base class implementation.
public virtual int Drive(TimeSpan time, int speed) { /* Method statements here */ return 0; }
// Derived classes must implement this.
public abstract double GetTopSpeed();
}
}
Motorcycle 类包括重载的方法,Drive。 这两个 Drive 方法的名称相同,但参数类型不同。
方法调用
方法可以是 实例,也可以 静态。 必须实例化对象以在该实例上调用实例方法;实例方法对该实例及其数据进行作。 通过引用方法所属的类型的名称来调用静态方法;静态方法不对实例数据进行作。 尝试通过对象实例调用静态方法将生成编译器错误。
调用方法类似于访问字段。 在对象名称(如果要调用实例方法)或类型名称(如果调用静态方法),添加句点、方法的名称和括号。 参数列在括号内,用逗号分隔。
方法定义指定所需的任何参数的名称和类型。 当调用方调用该方法时,它为每个参数提供具体值(称为参数)。 参数必须与参数类型兼容,但如果在调用代码中使用参数名称,则参数名称不必与方法中定义的参数相同。 在下面的示例中,Square 方法包含名为 int的 i 类型的单个参数。 第一个方法调用将 Square 方法传递给名为 intnum 类型的变量;第二个,数值常量;和第三个表达式。
public static class SquareExample
{
public static void Main()
{
// Call with an int variable.
int num = 4;
int productA = Square(num);
// Call with an integer literal.
int productB = Square(12);
// Call with an expression that evaluates to int.
int productC = Square(productA * 3);
}
static int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
}
值和引用参数
C# 中的类型是值类型或引用类型。 默认情况下,值类型和引用类型都按值传递给方法。
值类型参数
当值类型按值传递到方法时,会将对象的副本而不是对象本身传递给方法。 因此,当控件返回到调用方时,对所调用方法中的对象的更改对原始对象没有影响。
下面的示例按值将值类型传递给方法,调用的方法将尝试更改值类型的值。 它定义类型为 int的变量,它是一种值类型,将其值初始化为 20,并将其传递给名为 ModifyValue 的方法,该方法将变量的值更改为 30。 但是,当方法返回时,变量的值保持不变。
public static class ByValueExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(value);
Console.WriteLine("Back in Main, value = {0}", value);
}
static void ModifyValue(int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 20
当引用类型的对象按值传递给方法时,对对象的引用由值传递。 也就是说,该方法接收的不是对象本身,而是指示对象位置的参数。 如果使用此引用更改对象的成员,当控件返回到调用方法时,更改将反映在对象中。 但是,当控件返回到调用方时,替换传递给方法的对象对原始对象没有影响。
以下示例定义名为 SampleRefType的类(即引用类型)。 它实例化 SampleRefType 对象,为其 value 字段分配 44 个,并将对象传递给 ModifyObject 方法。 此示例实质上与上一个示例相同(它按值将参数传递给方法)。 但是,结果不同,因为使用引用类型而不是值类型。 对 ModifyObject 字段 obj.value 所做的修改也会更改参数 value 字段,rt。 当 Main 方法显示 rt 的值时,我们看到它已更新为 33,如示例所示。
public class SampleRefType
{
public int value;
}
public static class ByRefTypeExample
{
public static void Main()
{
var rt = new SampleRefType { value = 44 };
Console.WriteLine("In Main, rt.value = {0}", rt.value);
ModifyObject(rt);
Console.WriteLine("Back in Main, rt.value = {0}", rt.value);
}
static void ModifyObject(SampleRefType obj)
{
obj.value = 33;
Console.WriteLine("In ModifyObject, obj.value = {0}", obj.value);
}
}
// The example displays the following output:
// In Main, rt.value = 44
// In ModifyObject, obj.value = 33
// Back in Main, rt.value = 33
引用类型参数
如果要更改方法中的参数值,并且希望在控件返回到调用方法时反映该更改,则通过引用传递参数。 若要按引用传递参数,请使用 ref 或 out 关键字。 还可以通过引用传递值以避免复制,但仍会阻止使用 in 关键字进行修改。
以下示例与上一个示例相同,但值通过对 ModifyValue 方法的引用传递。 当参数的值在 ModifyValue 方法中修改时,当控件返回到调用方时,将反映值的变化。
public static class ByRefExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(ref value);
Console.WriteLine("Back in Main, value = {0}", value);
}
private static void ModifyValue(ref int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 30
ref 参数使用的常见模式涉及交换变量的值。 通过引用将两个变量传递给方法,该方法交换其内容。 以下示例交换整数值。
public static class RefSwapExample
{
static void Main()
{
int i = 2, j = 3;
Console.WriteLine("i = {0} j = {1}", i, j);
Swap(ref i, ref j);
Console.WriteLine("i = {0} j = {1}", i, j);
}
static void Swap(ref int x, ref int y) =>
(y, x) = (x, y);
}
// The example displays the following output:
// i = 2 j = 3
// i = 3 j = 2
通过传递引用类型参数,可以更改引用本身的值,而不是其单个元素或字段的值。
参数集合
有时,指定方法的确切参数数的要求是限制性的。 通过使用 params 关键字来指示参数是参数集合,允许使用可变数量的参数调用方法。 使用 params 关键字标记的参数必须是集合类型,并且它必须是方法的参数列表中的最后一个参数。
然后,调用方可以通过参数参数的四种方式之一调用该方法:
- 通过传递包含所需元素数的相应类型的集合。 该示例使用集合表达式,以便编译器创建适当的集合类型。
- 通过将相应类型的各个参数的逗号分隔列表传递给方法。 编译器创建适当的集合类型。
- 通过
null。 - 不向参数集合提供参数。
以下示例定义一个名为 GetVowels 的方法,该方法返回参数集合中的所有元音。
Main 方法说明了调用该方法的所有四种方式。 调用方不需要为包含 params 修饰符的参数提供任何参数。 在这种情况下,参数是空集合。
static class ParamsExample
{
static void Main()
{
string fromArray = GetVowels(["apple", "banana", "pear"]);
Console.WriteLine($"Vowels from collection expression: '{fromArray}'");
string fromMultipleArguments = GetVowels("apple", "banana", "pear");
Console.WriteLine($"Vowels from multiple arguments: '{fromMultipleArguments}'");
string fromNull = GetVowels(null);
Console.WriteLine($"Vowels from null: '{fromNull}'");
string fromNoValue = GetVowels();
Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
}
static string GetVowels(params IEnumerable<string>? input)
{
if (input == null || !input.Any())
{
return string.Empty;
}
char[] vowels = ['A', 'E', 'I', 'O', 'U'];
return string.Concat(
input.SelectMany(
word => word.Where(letter => vowels.Contains(char.ToUpper(letter)))));
}
}
// The example displays the following output:
// Vowels from array: 'aeaaaea'
// Vowels from multiple arguments: 'aeaaaea'
// Vowels from null: ''
// Vowels from no value: ''
注意
在 C# 13 之前,params 修饰符只能与单维数组一起使用。
返回方法的值
方法可以将值返回到调用方。 如果返回类型(方法名称之前列出的类型)未 void,则该方法可以使用 return 关键字返回值。 带有 return 关键字的语句,后跟与返回类型匹配的变量、常量或表达式将该值返回给方法调用方。 使用非 void 返回类型的方法需要使用 return 关键字返回值。
return 关键字也会停止执行该方法。
如果返回类型 void,则不带值的 return 语句仍可用于停止方法的执行。 如果没有 return 关键字,该方法在到达代码块的末尾时停止执行。
例如,这两种方法使用 return 关键字返回整数:
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2) =>
number1 + number2;
public int SquareANumber(int number) =>
number * number;
}
此示例使用表达式体形成员来分配方法的返回值。 此语法是使用单个语句(表达式)计算返回值的方法的简写。
还可以选择使用语句正文和 return 语句定义方法:
class SimpleMathExtnsion
{
public int DivideTwoNumbers(int number1, int number2)
{
return number1 / number2;
}
}
若要使用从方法返回的值,调用方法可以使用该方法调用本身,只要具有相同类型的值就足够了。 还可以将返回值分配给变量。 例如,以下三个代码示例实现了相同的目标:
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);
result = obj2.DivideTwoNumbers(6,2);
// The result is 3.
Console.WriteLine(result);
有时,你希望方法返回多个值。 使用 元组类型 和 元组文本 返回多个值。 元组类型定义元组元素的数据类型。 元组文本提供返回元组的实际值。 在以下示例中,(string, string, string, int) 定义 GetPersonalInfo 方法返回的元组类型。 表达式 (per.FirstName, per.MiddleName, per.LastName, per.Age) 是元组文本;该方法返回 PersonInfo 对象的第一个、中间和家族名称以及年龄。
public (string, string, string, int) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
然后,调用方可以使用以下代码使用返回的元组:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");
还可以将名称分配给元组类型定义中的元组元素。 以下示例演示使用命名元素的 GetPersonalInfo 方法的备用版本:
public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
然后,可以按如下所示修改对 GetPersonalInfo 方法的上一次调用:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");
如果方法采用数组作为参数并修改各个元素的值,则该方法不需要返回数组。 C# 按值传递所有引用类型,数组引用的值是指向数组的指针。
Expression-bodied 成员
通常,方法定义使用表达式的结果立即返回,或者将单个语句作为方法的主体返回。 可以使用 =>定义此类方法的语法快捷方式:
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);
如果方法返回 void 或异步方法,则方法的主体必须是语句表达式(与 lambda 相同)。 对于属性和索引器,它们必须是只读的,并且不使用 get 访问器关键字。