编写高质量代码改善C#程序的157个建议——建议25:谨慎集合属性的可写操作

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

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

    class Program
    {
        static List<Student> listStudent = new List<Student>()
            {
                new Student(){ Name = "Mike", Age = 1},
                new Student(){ Name = "Rose", Age = 2}
            };

        static void Main(string[] args)
        {
            StudentTeamA teamA = new StudentTeamA();
            Thread t1 = new Thread(() =>
            {
                teamA.Students = listStudent;
                Thread.Sleep(3000);
                Console.WriteLine(listStudent.Count); //模拟对
                //集合属性进行一些运算
            });
            t1.Start();
            Thread t2 = new Thread(() =>
            {
                listStudent = null;   //模拟在别的地方对list1而
                //不是属性本身赋值为null
            });
            t2.Start();
        }
    }

    class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    class StudentTeamA
    {
        public List<Student> Students { get; set; }
    }

上面代码的问题是:线程t1模拟将对类型StudentTeamA的Students属性进行赋值,它是一个可读/可写的属性。由于集合属性是一个引用类型,而当前针对该属性对象的引用却又两个,即集合本身和调用者的类型变量listStudent,线程t2也许是另一个程序员写的,但他看到的只有listStudent,结果,针对listStudent的修改hi直接影响到另一个工作线程的对象。在例子中,我们将listStudent赋值为null,模拟在StudentTeamA(或者说工作线程t1)不知情的情况下使得集合属性变为null。接着,线程t1模拟针对Students属性进行若干操作,导致抛出异常。

下面的StudentTeamA版本是一个改进过的版本。首先,将类型的集合属性设置为只读;其次,集合对象由类型自身创建,这保证了集合属性永远只有一个引用:

    class Program
    {
        static List<Student> listStudent = new List<Student>()
            {
                new Student(){ Name = "Mike", Age = 1},
                new Student(){ Name = "Rose", Age = 2}
            };
        
        static void Main(string[] args)
        {
            StudentTeamA teamA2 = new StudentTeamA();
            teamA2.Students.Add(new Student() { Name = "Steve", Age = 3 });
            teamA2.Students.AddRange(listStudent);
            Console.WriteLine(teamA2.Students.Count);
            //也可以像下面这样实现
            StudentTeamA teamA3 = new StudentTeamA(listStudent);
            Console.WriteLine(teamA3.Students.Count);
        }

    class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    class StudentTeamA
    {
        public List<Student> Students { get; private set; }

        public StudentTeamA()
        {
            Students = new List<Student>();
        }

        public StudentTeamA(IEnumerable<Student> studentList)
            : this()
        {
            Students.AddRange(studentList);
        }
    }

在改进版本的StudentTeamA中尝试对属性Students进行赋值:

 teamA.Students = listStudent;

将导致编译通不过。

转自:《编写高质量代码改善C#程序的157个建议》陆敏技

原文地址:https://www.cnblogs.com/jesselzj/p/4731270.html