-1

The code below is throwing System.StackOverflowException in VS2015 Update 3 (Resharper 2016, Windows 10 x64, .NET 4.5). IMO, it is due to the cyclic initialization in the static constructors of City -> Authority -> Country -> City. Strange enough, it is working with VS2015 Update 2.

This is the exception that I got in 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

I wonder why it works with Update 2 but not with Update 3, and more importantly, what can I do to fix it ? This the test code :

public class Program 
{
    public static void Main(string []args)
    {
        var p1 = PeopleTestData.GetByID(1);
        Console.WriteLine(p1.Name);
    }
}

Here are the initialization using Lazy and static constructors:

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);
    }
}

And the domain entity:

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;
}
Dio Phung
  • 5,944
  • 5
  • 37
  • 55
  • Your code contains some lambda expressions: they don't get executed where they are declared.When an actual value is requested, they must be executed. But I am not sure if there could be more complicated scheduling which is interfering here. – Bernhard Hiller Oct 18 '16 at 08:22

2 Answers2

0

You should not put any cyclic dependency for sure.

Are you sure it works everytime in Update2? I doubt.

Since you are using Lazy initialization, there might be an order of execution which goes through without cycle. You are too lucky to get it executed despite of having cyclic dependency.

Haseeb Jadoon
  • 450
  • 6
  • 20
0

Cyclic dependency of static constructors causes deadlock, not stack overflow. In your case, the static constructors have completed, but the lazy initialization has not.

Until the Lazy<T> instance has been fully initialized, a re-entrant attempt to initialize that instance will start all over. And that is where your stack overflow comes from.

Without a good Minimal, Complete, and Verifiable code example that reliably reproduces the problem (and yet still accurately reflects your scenario), it's impossible to provide a specific solution. But for sure, you need to not have the initialization of your types rely on each other's initialization. (And like the other answerer, I'm skeptical that this would have worked in any version of Visual Studio…it's not something that is even dependent on the VS version.)

You'll need to find a way for at least one of the types involved to complete its initialization and be useable for the other type, without it also depending on that other type.

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136