-2

I have an object that overrides iComparable and that also overrides toString().

When the constructor is called it is also calling toString(), but not hitting any break points in the toString() method.

The toString() is also called when each line of the constructor is executed, and as soon as the DataSource property is updated it gets the data from the database and puts in unexpected results.

I have proven this with the logging of environment.stacktrace for each call.

Is this expected behaviour, and is there anyway to only call the toString() when I explicitly call it rather than automatically.

Code for the class is below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Business.Data;

namespace PfWToEpayfactGIF
{
class PsPayChangeDetails : IComparable<PsPayChangeDetails>
{
  public DateTime EffectiveDate { get; set; }
  public String Code { get; set; }
  public string ChangeType { get; set; }

  public int DataSourceX { get; set; }

  private string gradeID;
  private string gradeSubCode;
  private string gradePercentage;
  private string IDField;
  private string salary;
  private string maxOTRateType;
  private string lWeightType;
  private string onTempPromotion;
  private string RTIHoursInd;
  private string paygroupID;
  private string classification;
  private string hoursPayable;
  private string workingPatternID;
  private string OSPSchemeNo;
  private string milestoneDate;
  private string employeeNo;


  public PsPayChangeDetails(string code, DateTime effectiveDate, string changeType, int groupSource)
  {
     EffectiveDate = effectiveDate;
     Code = code;
     ChangeType = changeType;
    DataSourceX = groupSource;
  }

  public void ReadLine(string[] line)
  {
     employeeNo = line[0] != string.Empty ? line[0] : employeeNo;
     try
     {
        if (Code == "E420")
        {
           gradeID = line[4] != string.Empty ? line[4] : gradeID;
           gradeSubCode = line[5] != string.Empty ? line[5] : gradeSubCode;
           gradePercentage = line[6] != string.Empty ? line[6] : gradePercentage;
           IDField = line[7] != string.Empty ? line[7] : IDField;
           salary = line[8] != string.Empty ? line[8] : salary;
           maxOTRateType = line[9] != string.Empty ? line[9] : maxOTRateType;
           lWeightType = line[10] != string.Empty ? line[10] : lWeightType;
           onTempPromotion = line[11] != string.Empty ? line[11] : onTempPromotion;
           RTIHoursInd = line[12] != string.Empty ? line[12] : RTIHoursInd;
        }
        else
        {

           paygroupID = line[4] != string.Empty ? line[4] : paygroupID;
           classification = line[5] != string.Empty ? line[5] : classification;
           hoursPayable = line[6] != string.Empty ? line[6] : hoursPayable;
           workingPatternID = line[7] != string.Empty ? line[7] : workingPatternID;
           OSPSchemeNo = line[8] != string.Empty ? line[8] : OSPSchemeNo;
           milestoneDate = line[9] != string.Empty ? line[9] : milestoneDate;
        }
     }
     catch (IndexOutOfRangeException)
     {
        //ignore the exception as its telling us we dont have all the fields which is fine.
     }

  }

  public override string ToString()
  {
     using (System.IO.StreamWriter write = new System.IO.StreamWriter(@"c:\temp\test.txt", true))
     {
        write.WriteLine(Environment.StackTrace);
     }
        string output = $"{employeeNo},{Code},{EffectiveDate.ToString("dd/MMM/yyyy")},{ChangeType},";
     if (Code == "E420")
     {
        output += $"{gradeID},{gradeSubCode},{gradePercentage},{IDField},{salary},{maxOTRateType},{lWeightType},{onTempPromotion},{RTIHoursInd}";
     }
     else
     {
        using (DataEntities db = new DataEntities(DataSourceX))
        {

           if (paygroupID == null)
           {
              string partTime = workingPatternID == "PT" ? "Y" : "N";
              paygroupID = db.PayGroupEESetting.Where(pge => pge.PartTimeInd == partTime && pge.EnteredDate == db.PayGroupEESetting.Where(pg => pg.PayGroup == pge.PayGroup && pg.EnteredDate <= EffectiveDate).Max(pg => pg.EnteredDate)).OrderBy(pge => pge.PayGroup).FirstOrDefault().PayGroup;
           }
           workingPatternID = workingPatternID == null ? "FT" : "PT";
           if (OSPSchemeNo == null)
           {
              OSPSchemeNo = db.OSPScheme.Min(o => o.SchemeNo).ToString();
           }
        }
        output += $"{paygroupID},{classification},{hoursPayable},{workingPatternID},{OSPSchemeNo},{milestoneDate}";
     }
     return output;
  }


  public int CompareTo(PsPayChangeDetails other)
  {
     if (this.Code == other.Code)
     {
        return this.EffectiveDate.CompareTo(other.EffectiveDate);
     }
     else
     {
        return this.Code.CompareTo(other.Code);
     }
  }
}
}

Thanks for any help.

Ben

Ben Whyall
  • 268
  • 3
  • 17
  • 7
    Please provide [a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) – Alfie Goodacre Dec 15 '16 at 14:07
  • Damn it alfie, I was just typing that... ;) –  Dec 15 '16 at 14:07
  • 1
    I can´t see where you call `ToString`. It isn´t automatically called only because it´s there, you have to call it somewhere. – MakePeaceGreatAgain Dec 15 '16 at 14:10
  • Hi, it is automatically getting called and that's the problem – Ben Whyall Dec 15 '16 at 14:14
  • 1
    I will add a better example shortly, I will try and reduce it down – Ben Whyall Dec 15 '16 at 14:15
  • 3
    Why are you _writing to a file_ AND _reading from a database_ in `ToString`? `ToString` can get called many, many times that you don't expect. I would strongly suggest using different methods for BOTH of those activities and not overriding base `object` methods that could get called outside of your control. – D Stanley Dec 15 '16 at 14:26
  • Dear god, talk about unexpected side effects from calling a method. – Dan Wilson Dec 15 '16 at 14:39
  • @DStanley the file write is there purely for debug purposes. The database read is because the default values are different depending on the client db. If I change it away from tostring, it works as expected only called when explicitly done so. – Ben Whyall Dec 15 '16 at 14:45
  • 1
    And then, you should avoid calling virtual (thus potentially overriden) methods in your constructors. This can have side-effects in itself, when your class is not guaranteed to be the most derived type (which means you must mark it as sealed). Find more info on that here: [link](http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor) – K. Berger Dec 15 '16 at 14:48
  • 1
    @BenWhyall OK, so what's wrong with using a different method and calling it explicitly? `ToString` is just supposed to represent your current object as a string. Since's it's an overload of `object.ToString` _any_ code that gets a reference yo your object and calls `ToString` will execute that code. That code can be on a different thread, attached processes (e.g. debugger), etc. which would make debugging very difficult. So if your question is "why is it called when I don't call it" you may never know. – D Stanley Dec 15 '16 at 14:48

1 Answers1

3

The toString() is also called when each line of the constructor is executed

I'm guessing you know this because you're stepping through the code in the debugger. If that's the case, then the debugger is calling ToString on your object to represent it in some grid or other output (local variables, watch, etc.). Since the debugger is in a different process it may not hit any breakpoints you have.

Bottom line is - Don't use ToString for anything other then just representing the current state of your object as a string variable. If you need to load related objects, format your object in a specific way, etc. then do that outside of ToString.

D Stanley
  • 149,601
  • 11
  • 178
  • 240