如果类型的属性中有集合属性,那么应该保证属性对象是由类型本身产生的。如果将属性设置为可写,则会增加抛出异常的几率。一般情况下,如果集合属性没有值,则它返回的Count等于0,而不是集合属性的值为null。下面的代码将产生一个NullReferenceException异常:
class Program
{
static List<Student>list1=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=list1;
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的修改会直接影响到另一个工作线程中的对象。在例子中,我们将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;
将会导致编译通不过。