如何避免StackOverflowException在静态构造函数与“懒惰”初始化大列表

本文关键字:懒惰 初始化 列表 StackOverflowException 何避免 静态 构造函数 | 更新日期: 2023-09-27 18:13:41

下面的代码在VS2015 Update 3 (Resharper 2016, Windows 10 x64, . net 4.5)中抛出System.StackOverflowException。在我看来,这是由于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

我想知道为什么它可以与更新2而不是更新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;
}

如何避免StackOverflowException在静态构造函数与“懒惰”初始化大列表

你肯定不应该放置任何循环依赖。

你确定它的工作每次在Update2?我怀疑。

由于您使用的是延迟初始化,因此可能存在不经过循环的执行顺序。尽管有循环依赖,您还是很幸运地执行了它。

静态构造函数的循环依赖导致死锁,而不是堆栈溢出。在您的例子中,静态构造函数已经完成,但是延迟初始化还没有完成。

直到Lazy<T>实例完全初始化,再入尝试初始化该实例将从头开始。就是你的堆栈溢出的来源。

如果没有一个良好的最小化、完整和可验证的代码示例来可靠地再现问题(并且仍然准确地反映您的场景),就不可能提供一个特定的解决方案。但可以肯定的是,您需要而不是让类型的初始化依赖于彼此的初始化。(和其他回答一样,我怀疑这是否能在任何版本的Visual studio中工作&帮助;这甚至不是依赖于VS版本的东西。)

您需要找到一种方法,使所涉及的至少一种类型完成初始化并可用于另一种类型,而不依赖于另一种类型。