3
\$\begingroup\$

This is another review on a program I've asked about before, now translated from VBA into C#. I'm sure I've brought over a lot of bad habits with me, so I'm spotlighting some key areas I'd love open-ended feedback on.

This is a WPF desktop app.


Program Function

As before, this program makes emergency changes to medical claim files, delivers all the corrected files analysts need, and produces a changelog as well for their review.


Main method - Fix names changed to protect the innocent

class FixTextFiles
{
    [System.STAThreadAttribute()]
    //[System.Diagnostics.DebuggerNonUserCodeAttribute()]
    //[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
    static void Main()
    {
        var model = new Model();
        ActiveFixes activeFixes = new ActiveFixes();
        ActiveReports activeReports = new ActiveReports();
        FileInfo[] files = null;


        if (!ModelIsSetUpBasedOnArgumentsOrUI(model, activeFixes, activeReports, ref files))
        {
            return;
        }


        var DoubleProgressBar = new ViewPlusViewModel.DoubleProgressBar();
        DoubleProgressBar.ProgressBarFilesCompleted.Maximum = files.GetUpperBound(0) + 1;
        DoubleProgressBar.Show();
        DoubleProgressBar.Activate();


        for (var i = files.GetLowerBound(0); i <= files.GetUpperBound(0); i++)
        {
            DoubleProgressBar.ProgressBarFilesCompleted.Value = i;
            DoubleProgressBar.LabelFilesCompleted.Content = "Files Completed: " + i + " / " + DoubleProgressBar.ProgressBarFilesCompleted.Maximum + ", " + files[i].Name;

            string entireFile = File.ReadAllText(files[i].FullName);
            if (entireFile != string.Empty)
            {
                var originalFileLines = entireFile.Split(new string[] { model.Delimiter1 }, StringSplitOptions.None);
                var revisedFileLines = entireFile.Split(new string[] { model.Delimiter1 }, StringSplitOptions.None);


                activeFixes.ResetIndicatorsOnNewFile();
                activeReports.ResetIndicatorsOnNewFile();


                DoubleProgressBar.ProgressBarLinesCompleted.Maximum = originalFileLines.GetUpperBound(0) + 1;


                // --- Begin Manipulation ---
                for (var currentLineNumber = originalFileLines.GetLowerBound(0); currentLineNumber <= originalFileLines.GetUpperBound(0); currentLineNumber++)
                {
                    DoubleProgressBar.ProgressBarLinesCompleted.Value = currentLineNumber;
                    DoubleProgressBar.LabelLinesCompleted.Content = "Lines Completed: " + currentLineNumber + " / " + DoubleProgressBar.ProgressBarLinesCompleted.Maximum;


                    // --- Ongoing variables ---
                    var currentLine = originalFileLines[currentLineNumber];

                    var segmentType = currentLine.Substring(0, currentLine.IndexOf(model.Delimiter3));


                    // --- Fixes ---
                    if (activeFixes.FixClassThatFixesIndividualLines1 != null)
                    {
                        // check things and do FixClass1 methods
                    }


                    if (activeFixes.FixClassThatFixesIndividualLines2 != null)
                    {
                        // check things and do FixClass2 methods
                    }


                    // repeat for 40+ fix/report classes
                }

                DoubleProgressBar.ProgressBarLinesCompleted.Value = DoubleProgressBar.ProgressBarLinesCompleted.Maximum;
                DoubleProgressBar.ProgressBarFilesCompleted.Value = i + 1;


                if (activeReports.Count == 0)
                {
                    if (model.CreateChangelogSheets) { WriteOriginalAndUpdatedSegmentsToCSVAndNoteDifferences(originalFileLines, revisedFileLines, files[i]); }
                    WriteUpdatedSegmentsToNewFile(revisedFileLines, model.Delimiter1, files[i], model.FixedFilesDestination);
                }


                if (activeReports.ReportClassThatReportsOnWholeFiles1 != null)
                {
                    // check things and do ReportClass1 methods
                }


                if (activeReports.ReportClassThatReportsOnWholeFiles2 != null)
                {
                    // check things and do ReportClass2 methods
                }
            }
        }


        activeFixes.ReprotectSettingsSheets();
        activeReports.ReprotectSettingsSheets();

        var tempDirectoryPath = Path.Combine(new string[] { Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "(Program Name)" });
        if (Directory.Exists(tempDirectoryPath))
        {
            try
            {
                Directory.Delete(tempDirectoryPath, true);
            }
            catch (IOException e)
            {

            }
        }

        UpdateTelemetryFile(files[0].DirectoryName, files.GetUpperBound(0) + 1, activeFixes, activeReports);

        if (model.LaunchedFromBatchFile == false) { MessageBox.Show("All files have been fixed and saved to the same folders as the source files with \"Fixed\" added to their filenames. No original files have been modified."); }
    }

This is mostly a console app with some light UI tacked on for easy user configuration - start getting ready for user config

    private static bool ModelIsSetUpBasedOnArgumentsOrUI(Model model, ActiveFixes activeFixes, ActiveReports activeReports, ref FileInfo[] files)
    {
        var commandLineArgs = Environment.GetCommandLineArgs();

        switch (commandLineArgs.GetUpperBound(0))
        {
            case 0:
                if (!UserPromptedSettingsWereWrittenToModel(ref model, ref activeFixes, ref activeReports))
                {
                    return false;
                }

                files = GetFilesToWorkWith();
                if (files.Length == 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            case 7:
                // Model is setup based on command line arguments instead
        }
    }

This is the major UI portion - user goes through a sequence of two main config windows (and potentially one child window) before selecting their files with GetFilesToWorkWith()

    public static bool UserPromptedSettingsWereWrittenToModel(ref Model model, ref ActiveFixes activeFixes, ref ActiveReports activeReports)
    {
        var viewModel = new ViewModel();
        viewModel.Setup();

        var tempDirectoryPath = Path.Combine(new string[] { Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "(Program name)", "Model Data" });
        if (!Directory.Exists(tempDirectoryPath))
        {
            Directory.CreateDirectory(tempDirectoryPath);
        }

        var tempFilePath = Path.Combine(new string[] { tempDirectoryPath, "Fixes Reports List.csv" });

        File.WriteAllText(tempFilePath, Properties.Resources.Fixes_Reports_List);

        viewModel.FixesReportsTable = GeneralTools.GetDataTableFromCSV(tempFilePath, "|", true, false);


        var parseSettings = new ViewPlusViewModel.ParseSettings();
        parseSettings.InitializeComponent();

        var fixSelector = new ViewPlusViewModel.FixSelector(viewModel);
        fixSelector.InitializeComponent();

        var seeAllFixesReports = new ViewPlusViewModel.SeeAllFixesReports(viewModel);
        seeAllFixesReports.InitializeComponent();


        parseSettings.ShowDialog();
        var nextWindowToOpen = "TBD";


        while (!string.IsNullOrEmpty(nextWindowToOpen) && !nextWindowToOpen.Equals("Select Text Files"))
        {
            switch (GetNextWindowToOpen(parseSettings, fixSelector, seeAllFixesReports))
            {
                case "Parse Settings":
                    parseSettings.ShowDialog();
                    break;
                case "Fix Selector":
                    fixSelector.ShowDialog();
                    break;
                case "See All Fixes And Reports":
                    if (fixSelector.FixesOrReports.Equals("Fixes"))
                    {
                        seeAllFixesReports.UpdateTableType("Fix");
                    }
                    if (fixSelector.FixesOrReports.Equals("Reports"))
                    {
                        seeAllFixesReports.UpdateTableType("Report");
                    }

                    seeAllFixesReports.ShowDialog();
                    break;
                case "Select Text Files":
                    nextWindowToOpen = "Select Text Files";
                    break;
                case null:
                    parseSettings.Close();
                    if (fixSelector.IsLoaded) fixSelector.Close();
                    if (seeAllFixesReports.IsLoaded) { seeAllFixesReports.Close(); }
                    return false;
            }
        }


        if (fixSelector.FixesOrReports.Equals("Fixes"))
        {
            activeFixes.Setup(fixSelector.ActiveFixes, viewModel.FixesReportsTable);
        }

        if (fixSelector.FixesOrReports.Equals("Reports"))
        {
            activeReports.Setup(fixSelector.ActiveReports, viewModel.FixesReportsTable);
        }


        model.Setup(parseSettings.Delimiter1, parseSettings.Delimiter2, parseSettings.Delimiter3, parseSettings.Delimiter4, Convert.ToBoolean(parseSettings.CreateChangelogSheets));

        parseSettings.Close();
        fixSelector.Close();
        if (seeAllFixesReports.IsLoaded) { seeAllFixesReports.Close(); }

        return true;
    }

Since I'm newing and ShowDialoging UIwindows from the code rather than Main()ing in a window in a more pure WPF fashion, I use code to determine where to go next

    private static string GetNextWindowToOpen(ViewPlusViewModel.ParseSettings parseSettings, ViewPlusViewModel.FixSelector fixSelector, ViewPlusViewModel.SeeAllFixesReports seeAllFixesReports)
    {
        if (fixSelector.GoBack)
        {
            fixSelector.GoBack = false;
            return "Parse Settings";
        }

        if (parseSettings.GoToNextWindow ||
            seeAllFixesReports.GoToNextWindow ||
            seeAllFixesReports.GoBack)
        {
            parseSettings.GoToNextWindow = false;
            seeAllFixesReports.GoToNextWindow = false;
            seeAllFixesReports.GoBack = false;
            return "Fix Selector";
        }

        if (fixSelector.GoToChildWindow)
        {
            fixSelector.GoToChildWindow = false;
            return "See All Fixes And Reports";
        }

        if (fixSelector.GoToNextWindow)
        {
            fixSelector.GoToNextWindow = false;
            return "Select Text Files";
        }

        return null;
    }

The first UI window, ParseSettings - users put in 5 values for the Model. Delimiter names changed to protect the innocent.

public partial class ParseSettings : Window
{
    public static readonly DependencyProperty Delimiter1Property = DependencyProperty.Register("Delimiter1", typeof(string), typeof(ParseSettings), new PropertyMetadata("(Default A)"));
    public static readonly DependencyProperty Delimiter2Property = DependencyProperty.Register("Delimiter2", typeof(string), typeof(ParseSettings));
    public static readonly DependencyProperty Delimiter3Property = DependencyProperty.Register("Delimiter3", typeof(string), typeof(ParseSettings), new PropertyMetadata("(Default B)"));
    public static readonly DependencyProperty Delimiter4Property = DependencyProperty.Register("Delimiter4", typeof(string), typeof(ParseSettings), new PropertyMetadata("(Default C)"));
    public static readonly DependencyProperty CreateChangelogSheetsProperty = DependencyProperty.Register("CreateChangelogSheets", typeof(string), typeof(ParseSettings), new PropertyMetadata("True"));

    public string Delimiter1
    {
        get { return GetValue(Delimiter1Property) as string; }
        set { SetValue(Delimiter1Property, value); }
    }
    public string Delimiter2
    {
        get { return GetValue(Delimiter2Property) as string; }
        set { SetValue(Delimiter2Property, value); }
    }
    public string Delimiter3
    {
        get { return GetValue(Delimiter3Property) as string; }
        set { SetValue(Delimiter3Property, value); }
    }
    public string Delimiter4
    {
        get { return GetValue(Delimiter4Property) as string; }
        set { SetValue(Delimiter4Property, value); }
    }
    public string CreateChangelogSheets
    {
        get { return GetValue(CreateChangelogSheetsProperty) as string; }
        set { SetValue(CreateChangelogSheetsProperty, value); }
    }
    public bool GoToNextWindow { get; set; }


    public ParseSettings()
    {
        InitializeComponent();
        GoToNextWindow = false;
    }


    protected void MakeChangelogSheetsButton_Clicked(object sender, RoutedEventArgs e)
    {
        var senderAsButton = sender as Button;

        if (senderAsButton == ButtonCreateChangelogSheets)
        {
            switch (this.CreateChangelogSheets)
            {
                case "True":
                    this.CreateChangelogSheets = "False";
                    senderAsButton.Background = Brushes.Pink;
                    break;
                case "False":
                    this.CreateChangelogSheets = "True";
                    senderAsButton.Background = Brushes.PaleGreen;
                    break;
            }
        }
    }


    private void SeeIfGoToNextWindowButtonCanBeEnabled(object sender, TextChangedEventArgs e)
    {
        if (Validation.GetHasError(this.InputDelimiter1) ||
            Validation.GetHasError(this.InputDelimiter2) ||
            Validation.GetHasError(this.InputDelimiter3) ||
            Validation.GetHasError(this.InputDelimiter4))
        {
            this.ButtonSelectFixesOrReports.IsEnabled = false;
        }
        else
        {
            this.ButtonSelectFixesOrReports.IsEnabled = true;
        }
    }


    private void GoToNextWindow_Click(object sender, RoutedEventArgs e)
    {
        if (sender == this.ButtonSelectFixesOrReports)
        {
            this.GoToNextWindow = true;
        }

        this.Hide();
    }
}

The second UI window, FixSelector - users can search for desired fixes or pick from a telemetry file-populated list of the most popular. Each fix listed in either search results or the popular options is a button in a ListView that can be clicked to activate the fix, change the button's color visually, and add or remove it from appropriate ListView-populating dictionaries

public partial class FixSelector : Window
{
    public static readonly DependencyProperty FixesOrReportsProperty = DependencyProperty.Register("FixesOrReports", typeof(string), typeof(FixSelector), new PropertyMetadata("Fixes"));
    public static readonly DependencyProperty SegmentFixNameSearchProperty = DependencyProperty.Register("SegmentFixNameSearch", typeof(string), typeof(FixSelector));
    public string FixesOrReports
    {
        get { return GetValue(FixesOrReportsProperty) as string; }
        set { SetValue(FixesOrReportsProperty, value); }
    }
    public string SegmentFixNameSearch
    {
        get { return GetValue(SegmentFixNameSearchProperty) as string; }
        set { SetValue(SegmentFixNameSearchProperty, value); }
    }
    public bool GoToNextWindow { get; set; }
    public bool GoToChildWindow { get; set; }
    public bool GoBack { get; set; }
    public List<(string FixOrReportName, long CountInTelemetryFile)> MostPopularFixes { get; set; }
    public List<(string FixOrReportName, long CountInTelemetryFile)> MostPopularReports { get; set; }
    public DataTable CompleteFixesReportsTable { get; set; }
    public DataTable CompatibilityTable { get; set; }
    public Dictionary<string, string> ActiveFixes { get; set; }
    public Dictionary<string, string> ActiveReports { get; set; }
    public Dictionary<Button, Button> ClickedButtonsByAddedButtonsInSelectedFixes { get; set; }
    public Dictionary<Button, Button> ClickedButtonsByAddedButtonsInSelectedReports { get; set; }
    public Dictionary<Button, Button> AddedButtonsInSelectedFixesByClickedButtons { get; set; }
    public Dictionary<Button, Button> AddedButtonsInSelectedReportsByClickedButtons { get; set; }


    public FixSelector(ViewModel viewModel)
    {
        InitializeComponent();
        GoToNextWindow = false;
        GoToChildWindow = false;
        GoBack = false;
        MostPopularFixes = viewModel.MostPopularFixes;
        MostPopularReports = viewModel.MostPopularReports;
        PopulateMostPopularOptionsListView();
        CompleteFixesReportsTable = viewModel.FixesReportsTable;
        CompatibilityTable = viewModel.CompatibilityTable;
        ActiveFixes = new Dictionary<string, string>();
        ActiveReports = new Dictionary<string, string>();
        ClickedButtonsByAddedButtonsInSelectedFixes = new Dictionary<Button, Button>();
        ClickedButtonsByAddedButtonsInSelectedReports = new Dictionary<Button, Button>();
        AddedButtonsInSelectedFixesByClickedButtons = new Dictionary<Button, Button>();
        AddedButtonsInSelectedReportsByClickedButtons = new Dictionary<Button, Button>();
    }


    private void PopulateMostPopularOptionsListView()
    {
        this.MostPopularOptions.Items.Clear();

        if (this.FixesOrReports.Equals("Fixes"))
        {
            if (!(this.MostPopularFixes is null))
            {
                for (var i = 0; i < this.MostPopularFixes.Count; i++)
                {
                    var button = new Button
                    {
                        Content = this.MostPopularFixes[i].FixOrReportName
                    };

                    button.Click += new RoutedEventHandler(this.IndividualFixReportButton_Click);

                    this.MostPopularOptions.Items.Add(button);
                }
            }
        }

        if (this.FixesOrReports.Equals("Reports"))
        {
            if (!(this.MostPopularReports is null))
            {
                for (var i = 0; i < this.MostPopularReports.Count; i++)
                {
                    var button = new Button
                    {
                        Content = this.MostPopularReports[i].FixOrReportName
                    };

                    button.Click += new RoutedEventHandler(this.IndividualFixReportButton_Click);

                    this.MostPopularOptions.Items.Add(button);
                }
            }
        }
    }


    protected void FixesOrReportsButton_Clicked(object sender, RoutedEventArgs e)
    {
        var senderAsButton = sender as Button;

        if (senderAsButton == ButtonFixesOrReports)
        {
            switch (this.FixesOrReports)
            {
                case "Fixes":
                    this.FixesOrReports = "Reports";
                    senderAsButton.Background = Brushes.LightBlue;
                    PopulateMostPopularOptionsListView();
                    this.SelectedFixes.Visibility = Visibility.Hidden;
                    this.SelectedReports.Visibility = Visibility.Visible;
                    if (this.ActiveReports.Count == 0)
                    {
                        this.ButtonSelectTextFiles.IsEnabled = false;
                    }
                    break;
                case "Reports":
                    this.FixesOrReports = "Fixes";
                    senderAsButton.Background = Brushes.Orange;
                    PopulateMostPopularOptionsListView();
                    this.SelectedFixes.Visibility = Visibility.Visible;
                    this.SelectedReports.Visibility = Visibility.Hidden;
                    this.ButtonSelectTextFiles.IsEnabled = true;
                    break;
            }

            PopulateSearchResults(this.SearchResults, e as TextChangedEventArgs);
        }
    }


    protected void PopulateSearchResults(object sender, TextChangedEventArgs e)
    {
        this.SearchResults.Items.Clear();

        if (string.IsNullOrEmpty(this.SegmentFixNameSearch))
        {
            return;
        }

        string typeToReturn;
        if (this.FixesOrReports.Equals("Fixes"))
        {
            typeToReturn = "Fix";
        }
        else
        {
            typeToReturn = "Report";
        }

        var nameFilter = GetCompleteFixesReportsTableRowFilterNameExpression();

        this.CompleteFixesReportsTable.DefaultView.RowFilter =
             nameFilter + " AND FixOrReport = '" + typeToReturn + "'";

        var matchingFixesTable = this.CompleteFixesReportsTable.DefaultView.ToTable();

        var matchingFixesList = new List<(string Name, string Description)>();
        for (var i = 0; i < matchingFixesTable.Rows.Count; i++)
        {
            matchingFixesList.Add((Name: matchingFixesTable.Rows[i]["Name"].ToString(), Description: matchingFixesTable.Rows[i]["Description"].ToString()));
        }

        for (var i = 0; i < matchingFixesList.Count; i++)
        {
            var button = new Button
            {
                Content = matchingFixesList[i].Name,
                ToolTip = matchingFixesList[i].Description
            };

            button.Click += new RoutedEventHandler(this.IndividualFixReportButton_Click);

            if (typeToReturn.Equals("Fix"))
            {
                if (this.ActiveFixes.ContainsKey(button.Content.ToString()))
                {
                    button.Background = Brushes.PaleGreen;
                }
            }

            if (typeToReturn.Equals("Report"))
            {
                if (this.ActiveReports.ContainsKey(button.Content.ToString()))
                {
                    button.Background = Brushes.PaleGreen;
                }
            }

            this.SearchResults.Items.Add(button);
        }
    }


    private void IndividualFixReportButton_Click(object sender, RoutedEventArgs e)
    {
        var senderAsButton = sender as Button;

        if (senderAsButton.Background == Brushes.PaleGreen)
        {
            senderAsButton.Background = (Brush)new BrushConverter().ConvertFrom("#FFDDDDDD");
            RemoveFixOrReportFromAppropriateDictionary(senderAsButton);
        }
        else
        {
            senderAsButton.Background = Brushes.PaleGreen;
            AddFixOrReportToAppropriateDictionary(senderAsButton);
        }
    }


    private void GoToNextWindow_Click(object sender, RoutedEventArgs e)
    {
        if (sender == ButtonSelectTextFiles)
        {
            this.GoToNextWindow = true;
        }

        if (sender == ButtonBackToParseSettings)
        {
            this.GoBack = true;
        }

        if (sender == ButtonSeeAllFixesReports)
        {
            this.GoToChildWindow = true;
        }

        this.Hide();
    }
}

Last major section: Based upon the dictionary of selected fixes or reports, here's how those ActiveFixes/ActiveReports classes get populated

   class ActiveFixes
    {
        public long Count { get; set; }
        public DataTable FixesReportsTable { get; set; }
        public Fixes.FixClass1 FixClass1 { get; set; }
        public Fixes.FixClass2 FixClass2 { get; set; }
        // etc. for 40+ fixes

        public ActiveFixes()
        {
            Count = 0;
        }


        public void Setup(Dictionary<string, string> selectionsFromFixSelector, DataTable fixesReportsTable)
        {
            this.FixesReportsTable = fixesReportsTable;

            InstantiateSelectionsAsProperties(selectionsFromFixSelector);

            var properties = this.GetType().GetProperties();

            for (var i = properties.GetLowerBound(0); i <= properties.GetUpperBound(0); i++)
            {
                if (!properties[i].Name.Equals("Count") && !properties[i].Name.Equals("FixesReportsTable"))
                {
                    if (properties[i].GetValue(this) != null)
                    {
                        this.Count += 1;

                        var activeFix = properties[i].GetValue(this) as IFixReport;
                        var fixName = ConversionMethods.GetFixReportNameFromIFixReportName(properties[i].Name);
                        var fixRows = this.FixesReportsTable.Select("Name = '" + fixName + "'");
                        activeFix.Settings = _(ProgramName)Tools.GetSettingsSheet(fixRows[0]["SettingsSheet"].ToString());
                        if (activeFix.Settings != null) { 
                            activeFix.Settings.Unprotect();
                            activeFix.ConfirmAndSpeedUpSettingsSheet();
                        }
                        activeFix.Setup();
                    }
                }
            }
        }


        public void InstantiateSelectionsAsProperties(Dictionary<string, string> selectionsFromFixSelector)
        {
            foreach (var key in selectionsFromFixSelector.Keys)
            {
                switch (key)
                {
                    case "Fix Class 1 Full Name":
                        this.FixClass1 = new Fixes.FixClass1();
                        break;
                    case "Fix Class 2 Full Name":
                        this.FixClass2 = new Fixes.FixClass2();
                        break;
                    // etc.
                }
            }
        }
\$\endgroup\$
2
\$\begingroup\$

A couple of small pointers. I'm skipping over larger architecture issues because it seems that this code is not the complete example of working code.

  1. Local variables are usually started with lower case letter, so I would suggest renaming DoubleProgressBar to doubleProgressBar
  2. You are iterating over files with this for (var i = files.GetLowerBound(0); i <= files.GetUpperBound(0); i++). Why not just use ordinary for (var i = 0; i < files.Length; i++)?
  3. You are using some string in switch cases. I would suggest using Enums, because those are safer to change, even if they are not as expressive as a string can be. Also, with enums you get a better support from tools.
  4. In Main you seem to be using regular string concat ("a" + "b" + "c"). Consider using string interpolation or string.Format()
  5. In Main you are settings files = null and then passing it as a ref parameter. When you are initializing a value inside method ("multiple return values"), you should use out (see Int32.TryParse-for example). You could also use tuples to avoid out and ref (C# 7 onwards?).

On general note, I would avoid methods that do multiple things. Method ModelIsSetUpBasedOnArgumentsOrUI does answer to that question, but it also initializes the files. If possible, those should be done in separate methods to make to code easier to understand.

\$\endgroup\$
  • \$\begingroup\$ Thanks for these! Is there more that I could provide you with that would enable you to comment on larger architecture? \$\endgroup\$ – puzzlepiece87 Dec 11 '19 at 21:19
  • \$\begingroup\$ If I understood correctly, that code isn't currently compiling. At least case 7: is missing implementation and ActiveFixes-class seems to also miss the end of the file. The repository would of course be the best but I'm guessing that's proprietary code. \$\endgroup\$ – Hekku2 Dec 13 '19 at 10:41
  • \$\begingroup\$ case 7: is indeed missing implementation and you're right, I cut some uninteresting methods out of the end of the ActiveFixes class. You're also right that the code is proprietary, sorry! The program compiles and works well so far, but it's still in the piloting stages. Now's a good time for me as a brand new C# programmer to get feedback and rearchitect where beneficial. I've actually already decided while waiting for more answers on this question to do more work outside of the main FixTextFiles class, and to split things up more decisively between Fixes and Reports. \$\endgroup\$ – puzzlepiece87 Dec 13 '19 at 22:06
  • \$\begingroup\$ I understand. I added couple of things to my answer. \$\endgroup\$ – Hekku2 Dec 16 '19 at 13:49
  • \$\begingroup\$ I got the most value out of your additions. Thank you for coming back to this answer. \$\endgroup\$ – puzzlepiece87 Dec 16 '19 at 19:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.