CodeGym /课程 /C# SELF /方法重载 (Method Overloading)

方法重载 (Method Overloading)

C# SELF
第 21 级, 课程 1
可用

1. 入门

现实生活中很多操作就像多功能瑞士军刀:同一个命令可以用不同的工具来完成。比如,想象一下ATM机:你插卡——ATM让你输密码;你输手机号——ATM等你输入短信验证码。动作是一个——“验证用户”,但方式不一样。

编程里我们经常遇到类似的情况:看起来要做同一个操作,但数据类型或者参数数量可能不一样。比如,我们的方法要给人或者动物打招呼,或者要把2个、3个甚至10个整数加起来。

当然,你可以把方法名字都取不一样:SumTwo、SumThree、SumArray。但程序员都很懒(不是说懒是进步的动力嘛)。而且这样代码也不好读。

方法重载

方法重载就是让同一个方法名能处理不同参数组合的一种方式,方法名不变。这其实是多态的一种,但和继承没关系。

方法重载就是你可以在一个类(或者结构体)里写多个同名方法,但参数列表(类型、数量、顺序)不一样。

方法签名

C#里的方法签名就是方法名加上参数类型和顺序。返回类型不算在签名里! 这经常会让人踩坑(后面会说)。

2. 重载实战:简单例子

我们来写个Greeter类,可以用不同方式打招呼:只用名字、用名字和年龄,或者啥参数都没有。


public class Greeter
{
    // 不带参数的打招呼
    public void Greet()
    {
        Console.WriteLine("你好,世界!");
    }

    // 用名字打招呼
    public void Greet(string name)
    {
        Console.WriteLine($"你好,{name}!");
    }

    // 用名字和年龄打招呼
    public void Greet(string name, int age)
    {
        Console.WriteLine($"你好,{name}!你已经{age}岁啦?不错哦!");
    }
}

现在你可以随便调用这些方法,C#编译器会根据你传的参数数量和类型自动选对的版本。

var greeter = new Greeter();
greeter.Greet();                // 你好,世界!
greeter.Greet("安雅");           // 你好,安雅!
greeter.Greet("彼得", 23);      // 你好,彼得!你已经23岁啦?不错哦!

3. 参数类型和数量的区别

重载成立的条件是方法之间有这些不同:

  • 参数数量不一样,
  • 至少有一个参数类型不一样,
  • 参数类型顺序不一样(但这个要小心)。

我们再加一个只接收年龄的重载:


public void Greet(int age)
{
    Console.WriteLine($"这么大岁数,真酷!({age}岁)");
}

现在调用:

greeter.Greet(10);          // 这么大岁数,真酷!(10岁)

注意:如果方法只靠返回类型不同,是不能重载的。比如这样会报错:


// 编译错误!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
只靠返回类型重载是不行的!

编译器会说:“已经有Foo(string)了,来点新花样吧!”

4. 重载和C#标准库

重载不只是我们自己写的Greet。打开.NET文档里的Console.WriteLine看看:

签名 用途
WriteLine()
打印空行
WriteLine(string)
打印字符串
WriteLine(int)
打印整数
WriteLine(double)
打印小数
WriteLine(string, object)
格式化字符串(一个参数)
WriteLine(string, params object[])
格式化字符串(多个参数)

这些都是同一个方法——WriteLine的重载。现在你明白为啥你总能这样写了吧:

Console.WriteLine("随便一行");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("总和: {0}", 42);

编译器总能正确理解你的调用!

5. 编译器怎么选用哪个重载?

很简单:它看你实际传了什么类型和多少参数。下面这表一目了然:

调用 会用哪个版本?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

如果有歧义会怎样?

有时候情况会失控。比如有歧义的重载——编译器选不出来


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// 错误:歧义——到底用哪个Print?(两个都能匹配)

编译器会报歧义错误。这种情况最好别写参数数量一样、类型很接近的重载,不然编译器会懵。

6. params——可变参数数量

假如你想写个方法,能接收任意数量的数字。那就用params关键字吧。


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"总和: {sum}");
}
带可变参数数量的方法( params)

现在你可以这样用:

SumAll(1, 2, 3);           // 总和: 6
SumAll(10, 20);            // 总和: 30
SumAll();                  // 总和: 0

带params的方法可以和重载一起用,但别写那种让编译器分不清你想用哪个版本的重载。

7. 重载和参数修饰符(ref、out、in)

C#会把参数修饰符当成签名的一部分(比如void Foo(int a)和void Foo(ref int a)是两个不同的方法,可以同时存在):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
用 ref修饰符重载

不带ref的调用会进第一个版本,带ref的会进第二个:

int n = 5;
SetValue(n);     // n 还是 5(值被复制)
SetValue(ref n); // n 变成 100

8. 图示:什么是重载


      +----------+
      |  MyClass |
      +----------+
           |
           |            (方法片段)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
类里方法重载的结构图

如果用代码表示:

// 调用重载的Foo()方法:
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. 例子:在我们的应用里重载方法

我们继续完善我们的学习小程序,给动物类加个重载方法。


public class Animal
{
    public string Name { get; set; }

    // 发出声音的方法
    public virtual void MakeSound()
    {
        Console.WriteLine("某种奇怪的声音...");
    }

    // 重载方法:指定音量的声音
    public void MakeSound(int volume)
    {
        Console.WriteLine($"动物发出{volume}分贝的声音。");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪!");
    }

    // 重载方法:带音量的汪汪
    public void MakeSound(int volume)
    {
        Console.WriteLine($"汪!(音量: {volume}分贝)");
    }
}

试试这些调用:

Dog rex = new Dog();
rex.MakeSound();           // 汪!
rex.MakeSound(75);         // 汪!(音量: 75分贝)

注意:在子类(Dog)里我们重载了MakeSound(int volume),现在它有两个版本:带参数和不带参数。

10. 方法重载常见错误

错误1:只靠返回类型重载方法。
这是不行的——返回类型不算在方法签名里。重载必须靠输入参数的数量或类型,不是void还是int。

错误2:歧义重载让编译器懵逼。
参数数量一样、类型很接近的重载(比如int和double)会让编译器分不清。例子:Print(int a, double b)和Print(double a, int b),你写Print(1, 1)就会报歧义错误。

错误3:params和其他重载冲突。
带params的方法可能会抢走本来该给别的重载的调用。如果类型一样,编译器可能选错你想要的方法。

错误4:忘了ref和out算签名一部分。
Do(ref int x)和Do(out int x)是两个不同的重载。如果你没注意,很容易搞混,调用错方法。

2
任务
C# SELF, 第 21 级, 课程 1
已锁定
创建重载方法来计算图形的面积
创建重载方法来计算图形的面积
2
任务
C# SELF, 第 21 级, 课程 1
已锁定
格式化输出数据的方法
格式化输出数据的方法
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION