StackOverflowException в установщике с резервным свойством

ОБНОВЛЕНИЕ: я исправил проблему, с которой столкнулся, но я не знаю, почему эта ошибка создала трассировку стека. Трассировка стека привела меня в совершенно неправильном направлении. Если кто-нибудь может объяснить, что здесь происходит, я был бы признателен (и отметит ваш ответ как принятый). Обратите внимание, что мой оригинальный пост был удален.

У меня был следующий класс. Нерелевантные части были удалены:

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

У меня также был этот класс, который взял экземпляр класса выше и создал строку XML, используя значения из экземпляра:

class SecondClass
{
    private ClassName classnameInstance;

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

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

Вот клиентский код, сгенерировавший трассировку стека:

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

Наконец, вот трассировка стека:

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

Эта трассировка стека привела меня к мысли, что я неправильно реализовал геттер/сеттер для STPP.SettleDueDate. Я проверил их, и вспомогательная переменная и т. д. были правильными (я понимаю, обычные причины циклов в геттерах/сеттерах). Дальнейшая отладка показала мне, что трассировка стека фактически была сгенерирована, когда была вызвана эта строка PrepareRequest():

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

Я обнаружил, что неправильно реализовал геттер для STPPData.AccountTypeDescription, потому что создал вспомогательное свойство, которое использовал в установщике, но НЕ использовал резервное свойство в геттере:

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

Мой вопрос:

Почему трассировка стека StackOverflowException указывает мне на SettleDueDate.set(), когда ошибка на самом деле была внутри AccountTypeDescription.get()?

Примечание. Я новичок в C# и работаю с LAMP. Я немного упростил код, но не думаю, что удалил что-то важное.


person pb149    schedule 07.12.2012    source источник
comment
Не зная, что такое _settleDueDateDictionary (код), помочь практически невозможно.   -  person Erik Philips    schedule 07.12.2012
comment
+1 за StackOverflowException   -  person jenson-button-event    schedule 07.12.2012
comment
@Erik Philips - Хорошо, я добавил это в вопрос.   -  person pb149    schedule 07.12.2012
comment
Можете ли вы создать короткую, но полную программу, демонстрирующую проблему?   -  person Jon Skeet    schedule 07.12.2012
comment
Вы должны посмотреть на трассировку стека исключений - она ​​покажет цепочку методов, которая приводит к рекурсии.   -  person Knaģis    schedule 07.12.2012
comment
@Knagis - Если я не сказал, установщик SettleDueDate - это тот, который приводит к рекурсии.   -  person pb149    schedule 07.12.2012
comment
почему вы сохраняете значение даты в виде строки, а не в виде DateTime?   -  person Servy    schedule 07.12.2012
comment
@ Pete171 Pete171 - судя по опубликованному вами коду, это не представляется возможным - рекурсивных вызовов нет. Может быть, есть какой-то другой код, который вы пропустили, чтобы уменьшить его размер в вопросе?   -  person Knaģis    schedule 07.12.2012
comment
Из трассировки стека видно, что где-то в установщике должен быть SettleDueDate = value; (вместо _settleDueDate = value;) или что-то в этом роде.   -  person rsbarro    schedule 07.12.2012
comment
@Knagis - я не знаю, что это будет. У вас есть полное определение _settleDueDateDictionary геттера и сеттера SettleDueDate, а также частной _settleDueDate резервной переменной. Единственное, что я изменил, это то, как anIntParameterHere передается сеттеру. Я не думаю, что это может быть проблемой? Что еще я мог предоставить? ClassName в противном случае просто содержит другие геттеры/сеттеры и несколько других членов данных.   -  person pb149    schedule 07.12.2012
comment
@Servy - Возможно, потому что я новичок в C #! Но после формирования этой строки нам нужно интерполировать ее в XML с помощью XMLWriter, поэтому я думаю, что это может быть нормальным использованием?   -  person pb149    schedule 07.12.2012
comment
@leppie - Как и выше, новичок в C #, поэтому может делать несколько странных вещей ... Я посмотрю на это, как только это будет решено.   -  person pb149    schedule 07.12.2012
comment
@Pete171 Pete171 Вы должны хранить его как DateTime во всем приложении, а затем преобразовывать его в строку непосредственно перед сохранением в XML-файле, а не выполнять операции с ним во всем приложении как строку.   -  person Servy    schedule 07.12.2012
comment
@Knagis - я еще немного отладил и обнаружил, что приведенный выше код не является проблемным. Извиняюсь за то, что не дал остальную часть - действительно не видел, как то, что я пропустил, могло быть проблемой.   -  person pb149    schedule 07.12.2012
comment
Все: Я устранил проблему. Не знаю, почему StackOverflowException сказал то, что сделал. Проблема была в другом. Вопрос переписан, чтобы спросить, почему это произошло.   -  person pb149    schedule 10.12.2012
comment
@ Pete171 - Где код, который устанавливает SettleDueDate? Возможно ли, что вы передавали в него значение AccountTypeDescription и/или использовали его в качестве ключа словаря?   -  person Bobson    schedule 10.12.2012
comment
@Bobson - Пожалуйста, посмотрите третий блок кода (мой клиентский код). Одна из строк там (начинающаяся STPP.SettleDueDate = Convert.To...) — это та часть, которая устанавливает SettleDueDate. Спасибо.   -  person pb149    schedule 10.12.2012
comment
@ Pete171 - А, я пропустил, спасибо. У меня нет ничего полезного, извините.   -  person Bobson    schedule 10.12.2012
comment
@ Pete171 - я только что подумал. Можете ли вы скопировать небольшую часть вашего кода и воспроизвести это? И/или вы можете загрузить ZIP-файл всего каталога куда-нибудь, чтобы кто-нибудь еще попытался воспроизвести его?   -  person Bobson    schedule 12.12.2012


Ответы (2)


Ниже приведены несколько простых шагов отладки, которые должны сузить проблему.

  • Откройте класс со свойством SettleDueDate.
  • Щелкните правой кнопкой мыши имя свойства SettleDueDate.
  • Нажмите на пункт меню «Найти все ссылки».
  • Каждое место, где SettleDueDate установлено, т.е. SettleDueDate = "Something or other" добавляет точку останова
  • Запустите приложение и продолжайте работу, когда сработают точки останова, пока одна из них не сработает несколько раз подряд.
  • Когда вы обнаружили проблемную точку и код находится в точке останова, вместо продолжения используйте команды Step Out и Step over, чтобы проследить свой путь вверх по стеку, чтобы выяснить, где он рекурсивно назначается.
person Bryan Roberts    schedule 07.12.2012
comment
Спасибо за ответ, но я ставил некоторые точки останова каждый раз, когда упоминается SettleDueDate, но ни одна из точек останова даже не была достигнута! - person pb149; 08.12.2012

Этот код очень фрагментарен, и я не уверен, что полностью понимаю все связи. Я предполагаю, что ClassName == STPPData и SecondClass == STPPXml? Тем не менее, я попытался воспроизвести эту ошибку, используя VS2010 и .NET 4. Мне это не удалось — трассировка стека показала бесконечную рекурсию только внутри AccountTypeDescription.set(). Должно же чего-то не хватать.

Во-первых, очень интересны эти строки в трассировке стека:

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)

Кажется, они окончательно показывают внутренности SettleDueDate.set(), а не только бесконечные вызовы к нему. Словарь и поиск по хешу присутствуют. У вас определенно где-то ошибка. Однако у меня есть подозрение, что ваш исходный код не содержит ошибки. Что касается ответа @Bryan, установили ли вы свои точки останова внутри установщика SettleDueDate, а не в местах вашего кода, где вы его вызываете? Если вы используете Visual Studio, вы также можете отключить исключения, используя эту функцию.

Я увидел, что вы делаете что-то с веб-сервисами, и это сразу же навело меня на мысль о прокси-классах. Они могут очень сильно напоминать код, который вы пишете, но это не тот код, который вы пишете. Они могут устаревать. Насколько вы уверены, что это исключение было вызвано кодом, который вы написали и скомпилировали?

Во-вторых, веб-сервисы также наводят меня на мысль о сериализации — процессе, который часто вызывает методы получения и установки свойств без вашего ведома. В прошлом у меня были проблемы с попытками WCF сериализовать IEnumerables и с ужасными ошибками, хотя мой код был в порядке.

Кстати, в моей системе эта строка не скомпилировалась:

if (!_accountTypes.Contains(value))

Это заставило меня задуматься, используете ли вы Mono или другую IDE, чем я. Ты же ЛАМПА, в конце концов =)

Я знаю, что это не настоящий ответ (пока), но мне интересно, что вы об этом думаете? Любые другие подробности, которыми вы можете поделиться?

person killthrush    schedule 08.03.2013