2.4C#函数成员之方法(Method)

1.方法声明

返回类型 方法名<类型参数>(值参数, 默认值参数 = 值, ref 引用参数, out 输出参数, in 输入参数, params 参数数组) 
{
    //方法体
}
//Expression-bodied表达式声明法
返回类型 方法名<类型参数>(参数) => 表达式;

2.方法分类

2.1成员方法

名称修饰符
静态成员方法(static method)static 修饰
实例成员方法(instance method)static 修饰

2.2本地函数

本地函数是嵌套在类型里成员内部的private方法。

名称修饰符
静态本地函数(static local function)static 修饰
非静态本地函数(non-static local function)static 修饰

3.参数分类

参数名称修饰符
值参数
默认值参数
ref 引用参数ref 修饰
out 输出参数out 修饰
in 输入参数in 修饰
params 参数数组params 修饰

3.1 值参数(按值传递)

如果值参数是值类型,对方法内形参的更改不会反应到方法外的实参。

class PassingValByVal
{
    static void SquareIt(int x)
    // The parameter x is passed by value.
    // Changes to x will not affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(n);  // Passing the variable by value.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/

如果值参数是引用类型,对方法内形参的更改会反应到方法外的实参,但新对象会存储在新内存。

class PassingRefByVal
{
    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // This change affects the original element.
        pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);

        Change(arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

3.2 默认值参数(按值传递)

在值参数的基础上可以为形参赋予默认值,当无实参的时候,将调用这个形参的默认值传递。

3.3 ref引用参数(按引用传递)

ref 引用参数在传递之前需要先初始化,可以由调用方法读取或写入。

如果ref 引用参数是值类型,对方法内形参的更改会反应到方法外的实参。

class PassingValByRef
{
    static void SquareIt(ref int x)
    // The parameter x is passed by reference.
    // Changes to x will affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(ref n);  // Passing the variable by reference.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/

如果ref 引用参数是引用类型,对方法内形参的更改会反应到方法外的实参,但新对象依然存储在老内存。

class PassingRefByRef
{
    static void Change(ref int[] pArray)
    {
        // Both of the following changes will affect the original variables:
        pArray[0] = 888;
        pArray = new int[5] {-3, -1, -2, -3, -4};
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

3.4 out输出参数(按引用传递)

out 输出参数在传递之前未初始化,只可以由调用方法写入。

out 输出参数与ref 引用参数一样,可以是值类型,也可以是引用类型。

void OutArgExample(out int number)
{
    number = 44;
}

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     
// Output: 44

3.5 in输入参数(按引用传递)

in 输入参数在传递之前需要先初始化,只可以由调用方法读取,但不可以写入。

in 输入参数与ref 引用参数一样,可以是值类型,也可以是引用类型。

void InArgExample(in int number)
{
    // 取消下行的注释,会遇到 error CS8331
    //number = 19;
}

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);    
// Output: 44

注意:与 refout 不同,方法调用时不需要添加 in

当存在两个方法重载时,无in对应按值传递的方法,有in对应按引用传递的in方法。

当存在一个方法时,无in在实参类型可以隐式转换为形参类型时允许使用实参,有in不允许。

3.6 params参数数组

在方法声明中只允许有一个 params 参数数组,params 参数数组允许向方法传递数量不定的实参,参数类型必须是一维数组,位置必须放在最后一个。

params 参数数组的行为 与 作为参数的常规数组类型 完全相同。区别在于,常规数组类型传递的是单个数组实参,而 params 参数数组除此之外,还可以传递 数量不定的实参,此时,会使用包含给定的实参自动创建数组实例。

System.Console 类的 Write 和 WriteLine 方法是参数数组用法的典型示例。

public class Console
{
    public static void Write(string fmt, params object[] args) { }
    public static void WriteLine(string fmt, params object[] args) { }
    // ...
}
//会使用字符串参数后的所有的数量不定的实参自动创建数组实例。
int x, y, z;
x = 3;
y = 4;
z = 5;
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
//等同于编写以下代码:
//单个数组实参
int x = 3, y = 4, z = 5;

string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

4.参数传递方式

实参的数量需与形参的数量相同,虽然有时候看似没赋予实参,实际上是提供了null值或类型的默认值或其它值,总之必须有值。

实参的类型需与形参的类型兼容,所谓兼容指的是比如存在继承关系。

实参的传递不仅支持按位置从左往右依次传递方式,还支持按命名参数传递方式。

实参的传递不仅支持按值传递方式(方法接收的是调用者提供的值的副本),还支持按引用传递方式(方法接收的是调用者提供的变量地址本身)。

//按命名参数传递
方法(参数名称1: 实参1, 参数名称2: 实参2)

5.返回值分类

5.1按值返回

返回值按值返回(返回的是值),调用方对方法的返回值进行的修改不会反应到方法内return 后的变量。

5.2ref(按引用返回)

返回值按引用返回(返回的是变量的引用),调用方对方法的返回值进行的修改将会反应到方法内return ref引用的变量。

ref 关键字可用于方法返回类型前面+方法内部return后面,方法调用前面+局部变量类型前面。

public ref Person GetContactInformation(string fname, string lname)
{
    // ...method implementation...
    return ref p1;
}

//使用 局部变量,对p2的修改不会反应到p1
Person p2 = contacts.GetContactInformation("Brandie", "Best");

//使用ref 局部变量,对p2的修改将会反应到p1
ref Person p2 = ref contacts.GetContactInformation("Brandie", "Best");

在某些情况下,按引用访问值可避免潜在的高开销复制操作,从而提高性能。此时,ref关键字用于变量前面+局部变量类型前面。

ref VeryLargeStruct reflocal = ref veryLargeStruct;

5.3ref readonly(按引用返回)

在 ref readonly 方法返回中,readonly 修饰符表示该方法返回一个引用,且不允许向该引用写入内容。

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    public static ref readonly Point3D Origin => ref origin;

    // other members removed for space
}

需存储在 ref readonly局部变量。

ref readonly 类型 局部变量 = ref 方法();
ref readonly 类型 新局部变量 = ref 老局部变量;

6.重载(overload)

方法签名 包含方法修饰符、方法名称、类型参数数量、(方法参数的修饰符、类型和数量),返回类型 不是方法签名的一部分。

尽管 refout 和 in 参数修饰符被视为方法签名的一部分,但如果唯一的不同是 refout和 in 修饰符,则无法重载这两个方法。

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded
    // methods that differ only on in, ref and out".
    public void SampleMethod(in int i) { }
    public void SampleMethod(ref int i) { }
}

但是,当一个方法具有 refoutin参数,而另一个方法采用其它参数,则可以重载方法。

class InOverloads
{
    public void SampleMethod(in int i) { }
    public void SampleMethod(int i) { }
}

7.Lambda表达式(又称匿名函数 )

通常不必为参数指定类型,因为编译器可以根据 lambda 主体、参数类型以及其它因素来推断类型。

通常,Lambda 表达式的返回类型是显而易见的并且是推断出来的。 对于某些表达式,它不起作用,从 C# 10 开始,可以在参数前面指定 Lambda 表达式的返回类型。指定显式返回类型时,必须将参数括起来。

可在 lambda 参数前添加 async 修饰符,声明为异步lambda。

//表达式lambda,主体为表达式
[async][返回类型](参数) => 表达式;

//语句lambda,主体为语句块
[async][返回类型](参数) => { 语句块 };
//零个参数
() => expression;
//只有一个输入参数,括号可以省略
x => x * x;
//两个或更多参数使用逗号分隔
(x, y) => x == y;
//有时,编译器无法推断参数的类型,可以显式指定参数类型
(int x, string s) => s.Length > x;

8.匿名方法

delegate 运算符创建一个可以转换为委托类型的匿名方法:

Func<int, int, int> sum = delegate (int a, int b) { return a + b; };
Console.WriteLine(sum(3, 4));  // output: 7

使用 delegate 运算符时,可以省略参数列表。 如果这样做,可以将创建的匿名方法转换为具有任何参数列表的委托类型,如以下示例所示:

Action<int, double> introduce = delegate { Console.WriteLine("This is world!"); };
introduce(42, 2.7);

注意:与delegate关键字两回事,delegate 关键字是用来声明委托类型。

9.抽象方法、虚方法和重写方法

方法修饰符声明和实现
抽象方法abstract在抽象基类中声明但未实现的方法,必须在所有非抽象派生类中 重写 实现。
虚方法virtual在普通基类中声明和实现的方法,任何派生类 都可 重写或不重写 实现。
重写方法override在非抽象派生类中实现的方法,将 重写 具有相同签名的抽象方法或虚方法。注意:在重写情况下,refoutin是签名的一部分,相互之间不匹配。

由于抽象方法声明不提供实际的实现,因此没有方法主体,则方法签名后没有大括号 ({ }),且声明仅以分号结尾。

public abstract void MyMethod();

当派生类的对象类型转换成基类类型时,即使调用基类上的虚方法,会导致执行派生类的重写方法版本。

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = B;
A.DoWork();  // Also calls the new method.

10.分部(partial)方法

拆分一个方法的定义到两个或更多的文件中,每个文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组合起来。

分部类或结构可以包含分部方法,类的一个部分包含方法的签名,可以在同一部分或另一部分中定义实现。

// Definition in file1.cs
partial void OnNameChanged();

// Implementation in file2.cs
partial void OnNameChanged()
{
  // method body
}
//合并后
void OnNameChanged()
{
  // method body
}

11.扩展(extension)方法

扩展方法使你能够向已存在的系统或用户自定义的类或接口“添加”方法,而无需创建新的派生类型、重新编译或以其它方式修改原始类型。

11.1定义IMyInterface接口

namespace DefineIMyInterface
{
    public interface IMyInterface
    {
        void MethodB();
    }
}

11.2定义扩展方法

using DefineIMyInterface;

namespace Extensions
{
    //定义包含扩展方法的静态类
    //以下扩展方法可以被任何实现了IMyInterface接口的类的实例访问
    public static class Extension
    {
        //扩展方法被定义为静态方法
        //第一个参数指定扩展方法所要扩展的类型,此参数前面必须加上 this 修饰符
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}

11.3调用扩展方法

扩展方法被定义为静态方法,但它们是通过像扩展类型上的实例方法语法进行调用的。

扩展方法不能被重写。当编译器遇到与类或接口中实例方法具有相同签名的扩展方法时,它首先在该类型的实例方法中寻找匹配的方法。如果未找到任何匹配方法,编译器将搜索为该类型定义的任何扩展方法,并且绑定到它找到的第一个扩展方法。

using DefineIMyInterface;
using Extensions;

namespace ExtensionMethodsDemo1
{
    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {            
            A a = new A();
            a.MethodA(1);
            a.MethodA("hello");
            a.MethodB();

            B b = new B();
            b.MethodA(1);
            b.MethodA("hello");
            b.MethodB();
            
            C c = new C();
            c.MethodA(1);
            c.MethodA("hello");
            c.MethodB();
        }
    }
}
/* 输出:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    B.MethodB()
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

扩展方法和其所在的类都是static,也可以像所有其它 static 成员那样对其进行访问。

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this string str)
        {
            return str.Split(new char[] { ' ', '.', '?' },
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}
using ExtensionMethods;

//实例成员访问法
string s = "Hello Extension Methods";
int i = s.WordCount();

//静态成员访问法
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);

12.base修饰符

base调用基类上已被其它方法重写的方法。

base只可以在构造函数、实例方法、实例属性的访问器中使用,不可以在静态方法中使用。

因为一个派生类直接或间接派生自多个基类,base所访问的基类是派生类声明中直接指定的基类。

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //其它派生类代码
        //...
        //调用基类的方法
        base.DoWork();
    }
}

13.new修饰符

当新版本库中的基类添加派生类中已存在的方法时,在派生类方法上使用 new 修饰符声明具有相同名称和签名的方法即可隐藏基类的虚方法或普通方法。

override的区别是:override会覆盖基类中的抽象方法或虚方法,而new会让基类和派生类的方法同时存在,形成两个独立的方法。

如果 override 关键字和 new 关键字均未指定,派生类中的方法默认将隐藏基类中的冲突方法,但编译器会生成警告,但仍将编译代码。

当派生类的对象类型转换成基类类型时,如果调用基类上的方法,而不会执行派生类的new方法版本。

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

14.sealed修饰符

派生类可以使用 sealed 修饰符来停止虚拟继承。

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}
public class C : B
{
    public sealed override void DoWork() { }
}

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/csharp/csharplang/7871.html

(1)
上一篇 2021年2月4日 02:55
下一篇 2021年2月5日 19:24

相关推荐

发表回复

登录后才能评论