所谓成员,是指类型的字段和方法。在成员设计时,掌握一定原则可以避免设计上的陷阱。

1、不要为抽象类提供公开的构造方法

  抽象类可以有构造方法,但是抽象类不能实例化。如果编程人员没有制定构造方法,编译器会自动生成一个默认的protected构造方法。下面是一个标准的简单抽象类:

abstract class MyAbstractClass
{
protected MyAbstractClass( ) { }
}

  抽象类的构造方法不应该是public或internal的。抽象类设计的本意是只能让子类继承,而不是用于生成实例对象。如果抽象类是public或者internal的,它对于其他类型来说就是可见的,而这是不必要的,多余的。抽象类只需对子类可见即可。

2、可见字段应该重构为属性

  字段与属性有本质的区别,属性是方法。如下面的Person类型:

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

  编译器针对属性Name编译后,会生成一个字段和两个方法。

  属性相对于字段有如下优势:

    1)可以为属性添加代码。属性是方法,所以可以在方法内对设置或获取属性的过程进行编写代码控制。如事件支持等。

    2)可以让属性支持线程安全。要让属性变成线程安全的,可以让类型自身去实现。如果让字段支持线程安全,就只有依靠调用者本身实现。

    3)属性得到VS编译器支持,能实现自动属性的功能。自动属性的特点在LINQ中应用十分广泛,在匿名类型中,它只能实现只读的自动属性,但字段不支持。

    4)从设计的角度(面向对象),公开的字段也应该使用属性。改变字段的状态,类型不会被通知到;而改变属性的值,类型支持则会被通知。

  综上,如果一个类型存在一个可见字段,那么它应该被重构为属性。如果某个属性只对内部可见,但不涉及上面4点,则建议使用字段。

3、区别对待override和new

  override和new使类型体系因为继承而呈现出多态性。多态是“面向对象语言”的三个重要特性之一。多态要求子类具有与基类方法同名的方法,而override和new的有如下作用:

    1)如果子类中的方法前面带有new关键字,则该方法被定义为独立于基类的方法。

    2)如果子类中的方法前面带有override关键字,则子类的对象将调用该方法,而不是调用基类的方法。

  如果,对于父类的方法在子类中使用了new关键字,则两个方法相互独立。此时,使用子类类型的对象调用方法时,程序执行的将是子类类型new的方法代码;而如果将子类类型转换为父类类型后,对象调用方法时将执行的是父类的方法代码。

  如果使用了override关键字重写方法,那么不论子类类型的对象是否转换为父类类型,调用方法时都将执行的是子类的代码。

  如果对于子类中,声明与父类相同函数名称的方法,但并不使用关键字new和override。编译器在编译后会提出警告,但不影响程序运行。此时,编译器会默认为是new的效果,所以输出和显示设置与new的效果一样。

4、避免在构造方法中调用虚成员

  在构造方法中调用虚成员会出现意想不到的错误。

 class Program
{
static void Main(string[] args)
{
Chinese chinese = new Chinese();
}
} class Person
{
public Person()
{
InitSkin();
} protected virtual void InitSkin()
{
//省略
}
} class Chinese:Person
{
Rece Rece; public Chinese():base()
{
Rece = new Rece() { Name = "赵铭" };
} protected override void InitSkin( )
{
Console.WriteLine(Rece.Name);
}
} class Rece
{
public string Name { get; set; }
}

  运行该示例,会出现NullReferenceException:未将对象引用设置到对象的实例。

  在调用代码中,需要创建一个Chinese的实例对象chinese。由于Chinese类型有基类Person,所以运行时首先调用基类的构造方法。在基类的构造方法中,构造函数会调用InitSkin虚方法。在程序运行时,调用的是子类的InitSkin方法。在子类的InitSkin方法中又在使用子类的Rece变量。但这个时候,子类的构造函数还没调用,因此Rece变量未实例化,但是InitSkin方法又在使用Rece变量,导致错误。

5、成员应优先考虑公开的基类型或接口

  类型成员在优先考虑公开基类型或接口,会使得类型支持更多的应用场合。

  FCL中的集合类型根据功能划分有List<T>、Dictionary<TKey, TValue>、HashSet<T>等。例如,需要清空集合中的元素,返回空集合的方法Empty,如果不返回基类型或者接口的情况下,就要求我们为每个集合类型都实现该方法。但是,在FCL中实现了一个静态类型Enumerable,代码如下:

 public static IEnumerable<TResult> Empty<TResult>()
{
return EmptyEnumerable<TResult>.Instance;
}

  使用了泛型接口IEnumerable,所以所有集合子类都可以不实现自己的Empty方法,做到项目的灵活应用。

6、重写时不应使用子类参数

  重写时,如果使用了子类参数,可能会偏离设计者的预期目标。

  如存在以下继承体系:

class Employee
{
} class Manager:Employee
{
} class Salary
{
public void SetSalary(Employee e)
{
Console.WriteLine("职员被设置了薪水。");
}
} class ManagerSalary:Salary
{
public void SetSalary(Manager e)
{
Console.WriteLine("经理被设置了薪水。");
}
}

  类型ManagerSalary中的SetSalary方法重写了Salary中的相同方法,但是参数采用了一个子类的参数。现在在程序中调用代码如下:

public static void Main()
{
ManagerSalary m = new ManagerSalary();
m.SetSalary(new Employee());
}

  设计者的本意时为经理设置对应的薪水,但是实际调用的代码却设置了员工的薪水。因此,在重写时使用子类参数有一定的风险。正确的方法时仍旧使用Employee类型参数,让编译器提醒我们要使用关键字new。

C#成员设计建议的更多相关文章

  1. NET设计规范二:类型成员设计

    http://www.cnblogs.com/yangcaogui/archive/2012/04/20/2459567.html 接着 → .NET设计规范一:设计规范基础 上一篇,我们来了解下类型 ...

  2. 《.NET 设计规范》第 5 章:成员设计

    <.NET 设计规范>第 5 章:成员设计 5.1 成员设计的通用规范 要尽量用描述性的参数名来说明在较短的重载中使用的默认值. 避免在重载中随意地改变参数的名字.如果两个重载中的某个参数 ...

  3. C# 《编写高质量代码改善建议》整理&笔记 --(五)成员设计

    1.可以字段应该重构为属性 2.谨慎将数组或集合作为属性 数组和集合作为属性存在会引起这样的一个分歧:如果属性是只读的,我们通常会认为他是不可改变的.但是如果将只读属性应用于数组和集合,而元素的内容和 ...

  4. 微信HTML5页面设计建议

    一个HTML5页面从提出到完成上线的流程:>   1.需求方.设计人员.H5实现人员三方共同讨论实现方案 2.设计人员出设计图 3.H5人员按设计图出H5页面 4.需求方评估已实现的H5页面后给 ...

  5. 写给“有钱大爷”、”美工殿下”、“前端文艺青年”的微信HTML5页面设计建议

    ==============================   2018更新 iphone X 的设计内容   ==============================     我保证你一分钟就 ...

  6. 使用bootstrap-table等自动使用ajax地址载入数据的插件的数据设计建议

    提出问题: bootstrap-table 可以根据ajax地址load的json数据.这个json数据一般就是数据库中查询的结果,而数据库中存放的数据一般不是用户友好的,比如数据表示一般使用简洁id ...

  7. 编写高质量代码改善C#程序的157个建议——导航开篇

    前言 由于最近工作重心的转移,原来和几个同事一起开发的项目也已经上线了,而新项目就是在现有的项目基础上进行优化延伸扩展.打个比方,现在已经上线的项目行政案件的Web管理网站(代码还没那么多相比较即将要 ...

  8. 《编写高质量代码:改善C#程序的157个建议》源码下载

    ==== 目录 前 言第一部分 语言篇第1章 基本语言要素 / 2建议1:正确操作字符串 / 2建议2:使用默认转型方法 / 6建议3:区别对待强制转型与as和is / 9建议4:TryParse比P ...

  9. [JAVA设计模式]第一部分:接口、抽象类、设计原则

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

随机推荐

  1. **不使用tuple函数,将列表转为元组

    使用动态参数: l = [1, 1, 3, 4, 5] def f(*args): return args x = f(*l) print(x)

  2. Remote error: Provider not exported: DataSetProvider1

    Remote error: Provider not exported: DataSetProvider1 是服务端的问题,ServerMethodsUnit1.cpp窗体上添加DataSetProv ...

  3. Linux运维常见故障排查和处理的33个技巧汇总

    作为linux运维,多多少少会碰见这样那样的问题或故障,从中总结经验,查找问题,汇总并分析故障的原因,这是一个Linux运维工程师良好的习惯.每一次技术的突破,都经历着苦闷,伴随着快乐,可我们还是执着 ...

  4. 全面了解POI操作Microsoft Office(Word、Excel、PowerPoint)

    POI 与 Microsoft Office 1. POI 简介 POI 是 Apache 下的 Jakata 项目的一个子项目,主要用于提供 java 操作 Microsoft Office 办公套 ...

  5. VS2008调用VS2012的WCF服务的方式和遇到的问题

    1 用添加服务引用的懒方式 2 用http请求方式 3 客户端自己定义一个Contract,跟服务端的一样(可以只写要使用的方法,不用全部写完). 由于规范要求,前两种都不能用,后面根据同事的描述,产 ...

  6. 启动和关闭MySQL服务

    停止mysql服务,说来简单,但不知道的话,还真是挠头.在这和mysql入门的同学们共享:)  正确方法是,进入mysql的bin目录下,然后执行./mysqladmin -uroot -p shut ...

  7. 273. Integer to English Words数字转为单词

    [抄题]: Convert a non-negative integer to its english words representation. Given input is guaranteed ...

  8. 万能头文件#include<bits/stdc++.h>

    最近在打cf时赛后翻阅别人的代码总是会发现一个陌生而奇怪的头文件#include<bits/stdc++.h> 奇怪之处就在于基本上所有的代码只要用了这个头文件就不再写其他头文件了. 百度 ...

  9. 抓包工具Fidder移动端HTTP请求抓包详解

    第一步:下载神器Fiddler,下载链接: http://fiddler2.com/get-fiddler 下载完成之后,傻瓜式的安装一下了! 第二步:设置Fiddler打开Fiddler,     ...

  10. Linux arp命令

    一.简介 arp命令用于操作主机的arp缓冲区,可以用来显示arp缓冲区中的所有条目.删除指定的条目或者添加静态的ip地址与MAC地址对应关系. 二.语法 -a<主机>:显示arp缓冲区的 ...