前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编写高质量代码改善C#程序的157个建议[勿选List<T>做基类、迭代器是只读的、慎用集合可写属性]

编写高质量代码改善C#程序的157个建议[勿选List<T>做基类、迭代器是只读的、慎用集合可写属性]

作者头像
aehyok
发布2018-08-31 10:30:32
5390
发布2018-08-31 10:30:32
举报
文章被收录于专栏:技术博客技术博客

前言

  本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

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

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

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

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

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

代码语言:javascript
复制
public class Employee1:List<Employee>
代码语言:javascript
复制
public class Employee2:IEnumerable<Employee>,ICollection<Employee>

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

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

代码语言:javascript
复制
    public class Employee
    {
        public string Name { get; set; }
    }
代码语言:javascript
复制
    public class Employee1:List<Employee>
    {
        public new void Add(Employee item)
        {
            item.Name += "Changed";
            base.Add(item);
        }
    }

进行调用

代码语言:javascript
复制
        public static void Main(string[] args)
        {
            Employee1 employee1 = new Employee1() {
                new Employee(){Name="aehyok"},
                new Employee(){Name="Kris"},
                new Employee(){Name="Leo"}
            };
            IList<Employee> employees = employee1;
            employees.Add(new Employee(){Name="Niki"});
            foreach (var item in employee1)
            {
                Console.WriteLine(item.Name);
            }
            Console.ReadLine();
        }

结果竟然是这样

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

代码语言:javascript
复制
    public class Employee2:IEnumerable<Employee>,ICollection<Employee>
    {
        List<Employee> items = new List<Employee>();
        public IEnumerator<Employee> GetEnumerator()
        {
            return items.GetEnumerator();
        }

        ///省略
    }

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

代码语言:javascript
复制
        public static void Main(string[] args)
        {
            Employee2 employee1 = new Employee2() {
                new Employee(){Name="aehyok"},
                new Employee(){Name="Kris"},
                new Employee(){Name="Leo"}
            };
            ICollection<Employee> 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只负责将元素迭代出来进行显示:

代码语言:javascript
复制
            IMyEnumerable list = new MyList();
            IMyEnumerator enumerator = list.GetEnumerator();
            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
                Console.WriteLine(current.ToString());
            }
            Console.ReadLine();

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

代码语言:javascript
复制
            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。我们来看一段简单的代码:

代码语言:javascript
复制
    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public class StudentTeamA
    {
        public List<Student> Students { get; set; }
    }
    class Program
    {
        static List<Student> list = new List<Student>() 
        {
            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属性进行若干操作,导致异常的抛出。

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

代码语言:javascript
复制
    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public class StudentTeamA
    {
        public List<Student> Students { get;private set; }
        public StudentTeamA()
        {
            Students = new List<Student>();
        }
        public StudentTeamA(IEnumerable<Student> list):this()
        {
            Students.AddRange(list);

        }
    }
    class Program
    {
        static List<Student> list = new List<Student>() 
        {
            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进行赋值,就会发现如下问题

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

英语小贴士

1、I have an outing plan tommorrow。 ——明天我有一个徒步的计划。

2、Amazing——使……人惊讶   unbelievable——令人不可思议

3、blue sky,white beach——蓝天白云

4、Could you please show me the way to cinema?——你可以向我展示去电影院的路吗?

5、go straight——直走 turn left——向左转 at the first cross——在第一个十字路口

作者:aehyok

出处:http://www.cnblogs.com/aehyok/

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,那不妨点个推荐吧,谢谢支持:-O。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2014-04-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档