[英]How to avoid StackOverflowException in static constructor with `Lazy` initialization of large lists
下面的代码在VS2015 Update 3 (Resharper 2016、Windows 10 x64、.NET 4.5)中抛出System.StackOverflowException
。 IMO,这是由于City
-> Authority
-> Country
-> City
的静态构造函数中的循环初始化。 奇怪的是,它正在使用 VS2015 Update 2。
这是我在 Visual Studio 中遇到的异常:
System.StackOverflowException {"Exception of type 'System.StackOverflowException' was thrown."}
Data : {System.Collections.ListDictionaryInternal}
Count: 0
IsFixedSize: false
IsReadOnly: false
IsSynchronized: false
Keys : {System.Collections.ListDictionaryInternal.NodeKeyValueCollection}
[]
SyncRoot : {object}
Values: {System.Collections.ListDictionaryInternal.NodeKeyValueCollection}
[]
HelpLink: null
HResult: -2147023895
InnerException: null
Message: Exception of type 'System.StackOverflowException' was thrown.
Source: null
StackTrace: null
TargetSite: null
我想知道为什么它适用于 Update 2 而不适用于 Update 3,更重要的是,我能做些什么来修复它? 这是测试代码:
public class Program
{
public static void Main(string []args)
{
var p1 = PeopleTestData.GetByID(1);
Console.WriteLine(p1.Name);
}
}
下面是使用Lazy
和静态构造函数的初始化:
public static class PeopleTestData
{
public static readonly Lazy<List<Person>> People;
static PeopleTestData()
{
People = new Lazy<List<Person>>(() => new List<Person>
{
new Person {Name = "Person1", City = CityTestData.GetByID(1), ID = 1},
new Person {Name = "Person2", City = CityTestData.GetByID(2), ID = 2},
//~6000 records
});
}
public static Person GetByID(int personID)
{
return People.Value.Single(p => p.ID == personID);
}
}
public static class CityTestData
{
public static readonly Lazy<List<City>> Cities;
static CityTestData()
{
Cities = new Lazy<List<City>>(() => new List<City>
{
new City {Name = "City1", Authority = AuthorityTestData.GetByID(1), Country = CountryTestData.GetByID(1), ID = 1},
new City {Name = "City2", Authority = AuthorityTestData.GetByID(2), Country = CountryTestData.GetByID(2), ID = 2},
//~5000 records
});
}
public static City GetByID(int cityID)
{
return Cities.Value.Single(p => p.ID == cityID);
}
}
public static class CountryTestData
{
public static readonly Lazy<List<Country>> Countries;
static CountryTestData()
{
Countries = new Lazy<List<Country>>(() => new List<Country>
{
new Country {Name = "Country1", Cities = CityTestData.Cities.Value.Where(c=>c.Country.ID == 1).ToList(), ID = 1},
new Country {Name = "Country2", Cities = CityTestData.Cities.Value.Where(c=>c.Country.ID == 2).ToList(), ID = 2},
//~200 records
});
}
public static Country GetByID(int countryID)
{
return Countries.Value.Single(p => p.ID == countryID);
}
}
public static class AuthorityTestData
{
public static readonly Lazy<List<Authority>> Authorities;
static AuthorityTestData()
{
Authorities = new Lazy<List<Authority>>(() => new List<Authority>
{
new Authority {Name = "Authority1", Country = CountryTestData.GetByID(1), ID = 1},
new Authority {Name = "Authority2", Country = CountryTestData.GetByID(2), ID = 2},
//~3000 records
});
}
public static Authority GetByID(int authorityID)
{
return Authorities.Value.Single(p => p.ID == authorityID);
}
}
和域实体:
public class Person
{
public int ID;
public string Name;
public City City;
}
public class City
{
public int ID;
public string Name;
public Country Country;
public Authority Authority;
}
public class Authority
{
public int ID;
public string Name;
public Country Country;
}
public class Country
{
public int ID;
public string Name;
public List<City> Cities;
}
你肯定不应该放置任何循环依赖。
你确定它每次都在 Update2 中工作吗? 我怀疑。
由于您使用的是延迟初始化,因此可能会有一个没有循环的执行顺序。 尽管有循环依赖,但你太幸运了,无法执行它。
静态构造函数的循环依赖导致死锁,而不是堆栈溢出。 在您的情况下,静态构造函数已完成,但延迟初始化尚未完成。
在Lazy<T>
实例完全初始化之前,初始化该实例的可重入尝试将重新开始。 这就是您的堆栈溢出的来源。
如果没有一个可靠地重现问题的良好的最小、完整和可验证的代码示例(但仍然准确地反映您的场景),就不可能提供特定的解决方案。 但是可以肯定,你需要没有你的类型的初始化互相依赖的初始化。 (和其他回答者一样,我怀疑这在任何版本的 Visual Studio 中都有效……它甚至不依赖于 VS 版本。)
您需要为所涉及的至少一种类型找到一种方法来完成其初始化并可用于其他类型,而无需它也依赖于其他类型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.