博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编写高质量代码改善C#程序的157个建议[勿选List<T>做基类、迭代器是只读的、慎用集合可写属性]...
阅读量:5334 次
发布时间:2019-06-15

本文共 6399 字,大约阅读时间需要 21 分钟。

原文:

前言

  本文已更新至 。本文主要学习记录以下内容:

  建议23、避免将List<T>作为自定义集合类的基类 

  建议24、迭代器应该是只读的

  建议25、谨慎集合属性的可写操作

建议23、避免将List<T>作为自定义集合类的基类

 如果要实现一个自定义的集合类,最好不要以List<T>作为基类,而应该扩展相应的泛型接口,通常是Ienumerable<T>和ICollection<T>(或ICollection<T>的子接口,如IList<T>。

public class Employee1:List
public class Employee2:IEnumerable
,ICollection

不过,遗憾的是继承List<T>并没有带来任何继承上的优势,反而丧失了面向接口编程带来的灵活性,而且可能不稍加注意,隐含的Bug就会接踵而至。

来看一下Employee1为例,如果要在Add方法中加入一点变化

public class Employee    {        public string Name { get; set; }    }
public class Employee1:List
{ public new void Add(Employee item) { item.Name += "Changed"; base.Add(item); } }

进行调用

public static void Main(string[] args)        {            Employee1 employee1 = new Employee1() {                new Employee(){Name="aehyok"},                new Employee(){Name="Kris"},                new Employee(){Name="Leo"}            };            IList
employees = employee1; employees.Add(new Employee(){Name="Niki"}); foreach (var item in employee1) { Console.WriteLine(item.Name); } Console.ReadLine(); }

结果竟然是这样

这样的错误如何避免呢,所以现在我们来来看看Employee2的实现方式

public class Employee2:IEnumerable
,ICollection
{ List
items = new List
(); public IEnumerator
GetEnumerator() { return items.GetEnumerator(); } ///省略 }

这样进行调用就是没问题的

public static void Main(string[] args)        {            Employee2 employee1 = new Employee2() {                new Employee(){Name="aehyok"},                new Employee(){Name="Kris"},                new Employee(){Name="Leo"}            };            ICollection
employees = employee1; employees.Add(new Employee() { Name = "Niki" }); foreach (var item in employee1) { Console.WriteLine(item.Name); } Console.ReadLine(); }

运行结果

建议24、迭代器应该是只读的

 前端时间在实现迭代器的时候我就发现了这样一个问题,迭代器中只有GetEnumeratior方法,没有SetEnumerator方法。所有的集合也没有一个可写的迭代器属性。原来这里面室友原因的:

其一:这违背了设计模式中的开闭原则。被设置到集合中的迭代可能会直接导致集合的行为发生异常或变动。一旦确实需要新的迭代需求,完全可以创建一个新的迭代器来满足需求,而不是为集合设置该迭代器,因为这样做会直接导致使用到该集合对象的其他迭代场景发生不可知的行为。

其二:现在,我们有了LINQ。使用LINQ可以不用创建任何新的类型就能满足任何的迭代需求。

关于如何实现迭代器可以来阅读我这篇博文http://www.cnblogs.com/aehyok/p/3642103.html

现在假设存在一个公共集合对象,有两个业务类需要对这个集合对象进行操作。其中业务类A只负责将元素迭代出来进行显示:

IMyEnumerable list = new MyList();            IMyEnumerator enumerator = list.GetEnumerator();            while (enumerator.MoveNext())            {                object current = enumerator.Current;                Console.WriteLine(current.ToString());            }            Console.ReadLine();

业务类B出于自己的某种需求,需要实现一个新的针对集合对象的迭代器,于是它这样操作:

MyEnumerator2 enumerator2 = new MyEnumerator2(list as MyList);            (list as MyList).SetEnumerator(enumerator2);            while (enumerator2.MoveNex())            {                object current = enumerator2.Current;                Console.WriteLine(current.ToString());            }            Console.ReadLine();

问题的关键就是,现在我们再回到业务类A中执行一次迭代显示,结果将会是B所设置的迭代器完成输出。这相当于BG在没有通知A的情况下对A的行为进行了干扰,这种情况应该避免的。

所以,不要为迭代器设置可写属性。

建议25、谨慎集合属性的可写操作

 如果类型的属性中有集合属性,那么应该保证属性对象是由类型本身产生的。如果将属性设置为可写,则会增加抛出异常的几率。一般情况下,如果集合属性没有值,则它返回的Count等于0,而不是集合属性的值为null。我们来看一段简单的代码:

public class Student    {        public string Name { get; set; }        public int Age { get; set; }    }    public class StudentTeamA    {        public List
Students { get; set; } } class Program { static List
list = new List
() { new Student(){Name="aehyok",Age=25}, new Student(){Name="Kris",Age=23} }; static void Main(string[] args) { StudentTeamA teamA = new StudentTeamA(); Thread t1 = new Thread(() => { teamA.Students = list; Thread.Sleep(3000); Console.WriteLine("t1"+list.Count); }); t1.Start(); Thread t2 = new Thread(() => { list= null; }); t2.Start(); Console.ReadLine(); } }

首先运行后报错了

这段代码的问题就是:线程t1模拟将对类型StudentTeamA的Students属性进行赋值,它是一个可读/可写的属性。由于集合属性是一个引用类型,而当前针对该属性对象的引用却有两个,即集合本身和调用者的类型变量list。

  线程t2也许是另一个程序猿写的,但他看到的只有list,结果,针对list的修改会直接影响到另一个工作线程中的对象。在例子中,我们将list赋值为null,模拟在StudentTeamA(或者说工作线程t1)不知情的情况下使得集合属性变为null。接着,线程t1模拟针对Students属性进行若干操作,导致异常的抛出。

下面我们对上面的代码做一个简单的修改,首先,将类型的集合属性设置为只读,其次,集合对象由类型自身创建,这保证了集合属性永远只有一个引用:

public class Student    {        public string Name { get; set; }        public int Age { get; set; }    }    public class StudentTeamA    {        public List
Students { get;private set; } public StudentTeamA() { Students = new List
(); } public StudentTeamA(IEnumerable
list):this() { Students.AddRange(list); } } class Program { static List
list = new List
() { new Student(){Name="aehyok",Age=25}, new Student(){Name="Kris",Age=23} }; static void Main(string[] args) { StudentTeamA teamA = new StudentTeamA(); teamA.Students.AddRange(list); teamA.Students.Add(new Student() { Name="Leo", Age=22 }); Console.WriteLine(teamA.Students.Count); ///另外一种实现方式 StudentTeamA teamB = new StudentTeamA(list); Console.WriteLine(teamB.Students.Count); Console.ReadLine(); } }

修改之后,在StudentTemaA中尝试对属性Students进行赋值,就会发现如下问题

上面也发现了两种对集合进行初始化的方式。

 

posted on
2014-04-15 15:24 阅读(
...) 评论(
...)

转载于:https://www.cnblogs.com/lonelyxmas/p/3666369.html

你可能感兴趣的文章
解决jQuery的$符号的冲突问题
查看>>
使用Javascript Ajax 通信操作JSON数据 [下]
查看>>
永久重定向
查看>>
2.1
查看>>
python_根据"词库"进行“词联想”
查看>>
JSP九大内置对象及其作用和四大作用域详解
查看>>
C++封装库
查看>>
C# 使用 SmtpClient 发送邮件注意项
查看>>
C#多线程学习(五) 多线程的自动管理(定时器)
查看>>
Hadoop产生背景
查看>>
NOIP2011 观光公交 加强版
查看>>
Android WebView使用基础
查看>>
在一个非套接字上尝试了一个操作 解决方法
查看>>
Fastjson莫名的一个BUG
查看>>
各浏览器下载文件名不乱码的解决办法
查看>>
判断邮箱格式是否正确的代码
查看>>
13.安卓消息处理机制
查看>>
CentOS 6.4操作系统安装(基于Vmware)
查看>>
MySQL 触发器例子(两张表同步增加和删除)
查看>>
PopupWindow响应返回键的问题
查看>>