C# 类和继承(扩展方法)

扩展方法

在迄今为止的内容中,你看到的每个方法都和声明它的类关联。扩展方法特性扩展了这个边

界,允许编写的方法和声明它的类之外的类关联。

想知道如何使用这个特性,请看下面的代码。它包含类MyData,该类存储3个double类型

的值,并含有一个构造函数和一个名为Sum的方法,该方法返回3个存储值的和。

在现实世界的开发中,扩展方法是一个特别有用的工具。事实上,几乎整个LINQ库都是通

过扩展方法来实现的。

复制代码
class MyData
{
    private double D1;   //字段
    private double D2;
    private double D3;

    public MyData(double d1,double d2,double d3)   //构造韩
    {
        D1=d1;D2=d2;D3=d3;
    }

    public double Sum()      //方法
    {
        return D1+D2+D3;
    }
}

这是一个非常有限的类,但假设它还含有另外一个方法会更有用,该方法返回3个数据的平

均值。使用已经了解的关于类的内容,有几种方法可以实现这个额外的功能。

  • 如果你有源代码并可以修改这个类,你只需要为这个类增加这个新方法。
  • 然而,如果不能修改这个类(如这个类在一个第三方类库中),那么只要它不是密封的,
    你就能把它用作一个基类并在派生自它的类中实现这个额外的方法。

然而,如果不能访问代码,或该类是密封的,或有其他的设计原因使这些方法不适用,就不

得不在另一个使用该类的公有可用成员的类中编写一个方法。

例如,可以编写一个下面这样的类。下面的代码包含一个名为ExtendMyData的静态类,它

含有一个名为Average的静态方法,该方法实现了额外的功能。注意该方法接受MyData的实例作为参数。

csharp 复制代码
static class ExtendMyData 
{
    public static double Average(MyDat md)  //MyData类的实例
    {
        return md.Sum()/3;  .使用MyData的实例
    }
}

class Program
{
    static void Main()
    {
        MyData md=new MyData(3,4,5);
        Console.WriteLine("Average:{0}",ExtendMyData.Average(md));//MyData的实例 调用静态方法
    }
}

运行结果

复制代码
avergage=4

尽管这是非常好的解决方案,但如果能在类的实例自身上调用该方法,而不是创建另一个作用

于它的类的实例,将会更优雅。下面两行代码阐明了它们的区别。第一行使用刚展示的方法:在另

一个类的实例上调用静态方法。第二行展示了我们愿意使用的形式:在对象自身上调用实例方法。

csharp 复制代码
ExtendMyData.Avergage(md)  //静态调用形式
md.Average();              //实例调用形式

扩展方法允许你使用第二种形式,即使第一种形式可能是编写这种调用的正常方法。

通过对方法Average的声明做一个小小的改动,就可以使用实例调用形式。需要做的修改是

在参数声明中的类型名前增加关键字this,如下所示。把this关键字加到静态类的静态方法的

第一个参数上,把该方法从类ExtendMyData的常规方法改变为类MyData的扩展方法。现在两种

调用形式都可以使用。

扩展方法的重要要求如下。

  • 声明扩展方法的类必须声明为static。
  • 扩展方法本身必须声明为static。
  • 扩展方法必须包含关键字this作为它的一个参数类型,并在后面跟着它扩展的类的
    名称。
    图8-22阐明了扩展方法的结构。

下面的代码展示了一个完整的程序,包括类MyData和声明在类ExtendMyData中的扩展方法

Average。注意方法Average完全如同它是MyData的实例成员那样调用!图8-22阐明了这段代码。

类MyData和ExtendMyData共同起到期望类的作用,带有3个方法。

复制代码
namespace ExtensionMethods
{
    sealed class MyData
    {
        private double D1,D2,D3;
        public MyData(double d1,double d2,double d3)
        {
            D1=d1;D2=d2;D3=d3;
        }

        public double Sum()
        {
            return D1+D2+D3;
        }
    }

    static class ExtendMyData 
    {
        public static double Average(this MyData md)  //关键字和类型
        {
            return md.Sum()/3;//声明为静态的
        }
    }

    class Program
    {
        static void Main()
        {
            MyData md=new MyData(3,4,5);
            Console.WriteLine($"Sum: {md.Sum()}");
            Console.WriteLine("Average:{0}",md.Average());
        }
    }
}

输出结果

csharp 复制代码
Sum : 12
Average :4

命名约定

编写程序时会出现很多名称:类的名称、变量名称、方法名称、属性名称和许多其他名称。

阅读程序时,使用命名约定是为要处理的对象种类提供线索的重要方法。

第7章简单介绍过命名,现在你已经对类有了更多了解,我们可以给出更多细节了。表8-4

列出了3种主要的命名风格,以及它们在.NET程序中常见的使用方式。

可维护代码的一个重要支柱就是使用准确、自描述的变量名字(这个策略不太适合类似于

本书的图书、文章和示例代码,因为有其他的考虑)。对于变量名,不能过于追求简洁,否则

"欲速则不达"。

并不是所有人都认同这些命名约定,特别是前缀下划线。有些人认为前缀下划线非常有用,

有些人则认为它非常难看。微软本身对这个问题处理得也不够好。在推荐的命名约定中,微软没

有将前缀下划线作为一种选择,却在代码中大量使用本书将遵循微软官方推荐的命名约定,用Camel大小写作为私有和受保护的字段名称。

关于下划线还有一点要说明的是,它并不常出现在标识符的中间位置,不过事件处理程序除

外,这将在第15章介绍。

相关推荐
wdfk_prog6 分钟前
lely-canopen-rtt:为什么推荐在 RT-Thread 上使用 Lely CANopen 构建主站
java·运维·缓存·docker·容器
Token掘金室11 分钟前
JSON模式结构化输出报错
java·服务器·json
秋饼17 分钟前
Spring AI 2.0 接入 DeepSeek V4.1 Flash 生产级实战
java·ai·技术分享·后端开发
IT枫斗者枫哥17 分钟前
Java 分批导出仍然 OOM?用 32 MiB 堆复现三种 CSV 写法
java
shehuiyuelaiyuehao25 分钟前
算法43,外观数列,模拟算法+双指针
java·算法
lhldsg1 小时前
多商户团购系统源码:技术架构选型与二次开发实战指南
java·小程序·架构
亦暖筑序1 小时前
AgentScope Java 实战:@Tool 方法里到底该不该写业务逻辑?
java·agent·ai编程
脉动数据行情11 小时前
Java SpringBoot 国际期货批量采集实践 美原油 / 黄金 / 指数期货定时落库
java·开发语言·spring boot
Zane19941 小时前
写Stream时踩过的坑:中间操作不会真正执行,直到你调用这一个方法
java·后端
EXI-小洲1 小时前
Spring AI (第二章)大模型对话上下文记忆
java·人工智能·spring