Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StackOverflowException in setter with backing property

UPDATE: I have fixed the problem I was experiencing but I don't know why the bug generated the stack trace that it did. The stack trace lead me in the completely wrong direction. If anyone can explain what was happening here I would appreciate it (and will mark your answer as accepted). Note that my original post has been deleted.

I had the following class. Non-relevant parts of it have been removed:

class ClassName {
    private string[] _accountTypes = new string[2] {"ECOM", "MOTO"};

    private Dictionary<string, string> _settleDueDateDictionary = new Dictionary<string, string>() {
        {"0", "Process immediately."},
        {"1", "Wait 1 day"},
        {"2", "Wait 2 days"},
        {"3", "Wait 3 days"},
        {"4", "Wait 4 days"},
        {"5", "Wait 5 days"},
        {"6", "Wait 6 days"},
        {"7", "Wait 7 days"},
    };


    private string _settleDueDate;

    private string _accountTypeDescription;


    public string SettleDueDate
    {
        get
        {
            DateTime today = DateTime.Today;
            long settleDueDate = Convert.ToInt64(_settleDueDate);
            return today.AddDays(settleDueDate).ToString("MM/dd/yyyy");
        }
        set
        {   
            if (!_settleDueDateDictionary.ContainsKey(value)) {
                // TODO - handle
            }
            _settleDueDate = value;
        }
    }

    public string AccountTypeDescription
    {
        get {
            //return AccountTypeDescription; // This would cause infinite recursion (not referring to backing property).
            return _accountTypeDescription; // This fixed the StackOverflowException I was faxed with
        }
        set
        {
            if (!_accountTypes.Contains(value))
            {
                // TODO - handle
            }
            _accountTypeDescription = value;
        }
    }
}

I also had this class which took an instance of the class above and created an XML string using values from the instance:

class SecondClass
{
    private ClassName classnameInstance;

    public SecondClass(ClassName instance)
    {
        classnameInstance = instance;
    }

    public string PrepareRequest(XMLWriter writer)
    {
        writer.WriteElementString("accounttypedescription", classnameInstance.AccountTypeDescription);
    }
}

Here is the client code that generated the stack trace:

STPPData STPP = new STPPData();

STPP.SiteReference = _secureTradingWebServicesPaymentSettings.SiteReference;
STPP.Alias = _secureTradingWebServicesPaymentSettings.Alias;

STPP.SettleDueDate = Convert.ToString(_secureTradingWebServicesPaymentSettings.SettleDueDate);
STPP.SettleStatus = _secureTradingWebServicesPaymentSettings.SettleStatus;
STPPXml STPPXml = new STPPXml(STPP);

XmlWriterSettings settings = new XmlWriterSettings();
settings.Async = false;
var builder = new StringBuilder();

using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
    string xmlRequest = STPPXml.PrepareRequest(writer);
}

Finally, here is the stack trace:

mscorlib.dll!string.GetHashCode()
mscorlib.dll!System.Collections.Generic.GenericEqualityComparer<System.__Canon>.GetHashCode(SYstem.__Canon obj)
mscorlib.dll!System.Collections.Generic.Dictionary<string,string>.FindEntry(string key)
mscorlib.dll!System.Collections.Generic.Dictionary<System.__Canon,System.__Canon>.ContainsKey(System.__Canon key)
ClassName.SettleDueDate.set(string value)
ClassName.SettleDueDate.set(string value)
ClassName.SettleDueDate.set(string value)
// Infinite recursion of this call

This stack trace lead me to believe that I had incorrectly implemented the getter/setter for STPP.SettleDueDate. I checked them and the backing variable etc. was correct (the usual causes for loops in getters/setters, I understand). Further debugging showed me that the stack trace was actually generated when this line of PrepareRequest() was called:

writer.WriteElementString("accounttypedescription", STPPData.AccountTypeDescription);

I discovered that I had incorrectly implemented the getter for STPPData.AccountTypeDescription because I had created a backing property that I used in the setter but I was NOT using the backing property in the getter:

public string AccountTypeDescription
{
    get {
        //return AccountTypeDescription; // This would cause infinite recursion.
        return _accountTypeDescription; // This fixed the StackOverflowException
    }
    // setter omitted for clarity (it is in the examples above)
}

My question is:

Why did the stack trace of the StackOverflowException point me to SettleDueDate.set() when the bug was actually inside AccountTypeDescription.get()?

Note: I'm new to C# and am coming from a LAMP background. I have simplified the code a little but I do not think I have removed anything important.

like image 227
pb149 Avatar asked Dec 07 '12 15:12

pb149


2 Answers

This code is very fragmentary and I'm not sure I quite understand all the connections. I'm assuming that ClassName == STPPData and SecondClass == STPPXml? Nonetheless, I tried reproducing this bug using VS2010 and .NET 4. I was unable to - the stack trace showed infinite recursion only within AccountTypeDescription.set(). There has to be something missing.

First of all, these lines in the stack trace are very interesting:

mscorlib.dll!string.GetHashCode()
mscorlib.dll!System.Collections.Generic.GenericEqualityComparer<System.__Canon>.GetHashCode(SYstem.__Canon obj)
mscorlib.dll!System.Collections.Generic.Dictionary<string,string>.FindEntry(string key)
mscorlib.dll!System.Collections.Generic.Dictionary<System.__Canon,System.__Canon>.ContainsKey(System.__Canon key)

They appear to definitively show the innards of SettleDueDate.set(), not just infinite calls to it. The dictionary and hash lookup are present. You definitely have a bug somewhere in there. However I have a hunch that your source code does not contain the bug. In reference to @Bryan's answer, did you set your breakpoints inside the SettleDueDate setter as opposed to places in your code where you call it? If you're using visual studio, you can also break on exceptions using this feature.

I saw that you are doing stuff with web services and that immediately makes me think of proxy classes. They can look an awful lot like code you write, but it isn't code you write. They can go stale. How sure are you that this exception was thrown by code you wrote and compiled?

Secondly, web services also make me think of serialization, a process that will often call property getters and setters without your knowledge. I've had issues in the past with WCF trying to serialize IEnumerables and failing horribly even though my code was just fine.

As an aside, on my system this line did not compile:

if (!_accountTypes.Contains(value))

This made me wonder if you are using Mono or a different IDE than me. You are a LAMP guy after all =)

I know this isn't a real answer (yet), but I'm wondering what you make of this? Any other details you can share?

like image 29
killthrush Avatar answered Oct 23 '22 19:10

killthrush


Below are some simple debugging steps that should narrow down the problem

  • Open up the class with SettleDueDate property
  • Right click on the property name for SettleDueDate
  • Click on the menu item 'Find all references'
  • Every place that SettleDueDate is set ie 'SettleDueDate = "Something or other"' add a breakpoint
  • Run the application and keep continuing when breakpoints are hit till one gets hit multiple times in a row
  • When you found the offending point and the code is on a breakpoint instead of continuing use the Step Out command and Step over commands to trace your way back up the stack to find out where it is being assigned recursively
like image 126
Bryan Roberts Avatar answered Oct 23 '22 21:10

Bryan Roberts