1

Project type: C# Class Library being called from an ASP.NET application Framework version: 4.5.1

I've been searching all over about partial classes, and all references I have found is that they are a code smell but all of them I have found are talking about god classes which this is not. I need help deciding what the best approach would be for this scenario.

I know partial classes are generally for designer generated code but... I can't decide between the options listed below. Is my option 2 with a partial class a really bad idea?

I am writing a program to create atypical CSV files meaning the file does not contain columnar data. Each line contains different data and a varying number of columns.

Whichever option used each method needs access to two datasets and a Document class which contains a public List<string> which each method adds a line (string) to.

What I do know:

  • Each line has a non-unique type identifier such as "H00","H10","D50","D55"...

  • Each line has fixed number of columns based on the line type identifier Some lines are related to others while others are not.

  • There are about 25 (give or take 5) types of lines, so there would be about 25 files part of the partial class plus one for a constructor. This number could grow but not more than 40 at absolute most.

What I do not want:

  • One big class with a method for each line type.

  • One big method with a bunch of helper methods.

Option 1: Create a separate Class for each line type.

Option 1 problem: This creates lots of class instances.

Option 2: Create a partial class named "Line" with a separate *.cs file for each line type containing a method matching the line type it is creating. With this example, I think I would use class level properties for the datasets and document initialized in a constructor.

Option 2: Partial classes are considered a code smell because they are difficult to manage and make code more complex.

Typical method, left out parameters to keep this example simple:

public class Document
{
    //The document class would not be part of the partial class
    //The document has other properties, I left them out to keep it simple
    public List<string> Lines { get; set; }

    public Document()
    {
        this.Lines = new List<string>
    }
}

partial class LineMethods // file named: H01.cs
{
    public void H01(DataSet ds1, DataSet ds2, Document doc)
    {

        string[] fields = new string[3];
        fields[0] = "855";
        fields[1] = "value from datatable";
        fields[2] = "value from datatable";

        string line = string.Join(",", fields);
        doc.Lines.Add(line);
    }
}

partial class LineMethods // File named: H10
{
    public void H10(DataSet ds1, DataSet ds2, Document doc)
    {
        string[] fields = new string[27];
        fields[0] = "855";
        fields[10] = "other from datatable";
        fields[16] = "other data";

        //Line variable would be added to the "document.Lines.Add"
        string line = string.Join(",", fields);
        doc.Lines.Add(line);
    }
}

EDIT After Answer selected: @Hristo, Every line is required to execute for each file and some methods will require different parameters. The requirement of every line being created will change.

The decision on if a line is required depends on data in one of the datasets. For example, when I create invoices, if the invoice does not have a discount then I would not create a line for a discount or if an order has a shipping charge or not would determine if a line for a shipping charge would be written. I think it would be easiest to write and maintain if this logic was decided in the method creating the line. If the line is not needed then a zero length would would be returned and the line would then not be added to the Doc.Lines

Moses
  • 78
  • 6

1 Answers1

4

You should ask yourself what would be most manageable in the long run. What will be easiest to read and maintain.

Solution 1:

I'd go this way:

public class Document
{
    //The document class would not be part of the partial class
    //The document has other properties, I left them out to keep it simple
    public List<string> Lines { get; set; }

    public Document()
    {
        this.Lines = new List<string>
    }
}

public interface ILine
{
    string[] ProduceLine(DataSet ds1, DataSet ds2);
}

public class H01 : ILine
{
    public string[] ProduceLine(DataSet ds1, DataSet ds2)
    {
        string[] fields = new string[3];
        fields[0] = "855";
        fields[1] = "value from datatable";
        fields[2] = "value from datatable";

        return fields;
    }
}

public class H10 : ILine
{
    public string ProduceLine(DataSet ds1, DataSet ds2)
    {
        string[] fields = new string[27];
        fields[0] = "855";
        fields[10] = "other from datatable";
        fields[16] = "other data";

        return fields;
    }
}

var doc = new Document();

foreach (ILine line in lines)
{
    doc.Lines.Add(string.Join(",", line.ProduceLine(ds1, ds2)));
}

Edit (where lines come from):

Well, really depends on you. Simplest example:

var lines = new List<ILine>(new []
{
    new H10(),
    new H01()
});

It would better if you didn't have to KNOW about every single implementation and simply have Unity inject all of them in your constructor. But it really depends on your business case. How do you determine which of the implementations to use? Do you need all, in all cases? Etc.

e.g.:

public class MyWorker
{
  // This would require that you setup Inversion of Control container and use it to resolve MyWorker.
  // See: http://stackoverflow.com/questions/1961549/resolving-ienumerablet-with-unity
  public MyWorker(IEnumerable<ILine> lines)
  {
     this.lines = lines;
  }
}

Solution 2:

Alternatively, if you REALLY like your partial construct, you can use delegates like this:

delegate string[] ProduceLine(DataSet ds1, DataSet ds2);

partial class LineMethods // file named: H01.cs
{
    public string[] H01(DataSet ds1, DataSet ds2)
    {
        string[] fields = new string[3];
        fields[0] = "855";
        fields[1] = "value from datatable";
        fields[2] = "value from datatable";

        return fields;
    }
}

partial class LineMethods // File named: H10
{
    public string[] H10(DataSet ds1, DataSet ds2)
    {
        string[] fields = new string[27];
        fields[0] = "855";
        fields[10] = "other from datatable";
        fields[16] = "other data";

        return fields;
    }
}

var h01Method = new ProduceLine(LineMethods.H01);
doc.Lines.Add(string.Join(",", h01Method.Invoke(ds1, ds2));

You decide what is easier to read and maintain.

hyankov
  • 4,049
  • 1
  • 29
  • 46
  • 1
    I was about to write an answer along the same lines. I don't understand why the OP is against option 1. There are only maximum 40 file types so a maximum of 40 objects - worrying about this smacks of premature optimization. Option 2 would mean the consumer(s) of the LineMethod class would need to have knowledge of every method in all of the partial classes, which could become a maintenance nightmare. – Nibor Feb 28 '17 at 13:19
  • I am not against anything really, I was just having a difficult time deciding. – Moses Feb 28 '17 at 13:36
  • Hristo, thanks for replying, I like your first answer with the usage of the interface. Though I am a bit confused on the implentation with the foreach(ILine in lines). I don't get where lines comes from. – Moses Feb 28 '17 at 13:41
  • 1
    The main difference is that in the case of concrete methods/delegates (partials, in your implementation), the "consumer" has to know about them. In the other, you could even dynamically get instances of all available implementations. Additionally, you could have 'special cases' where particular implementation (e.g. `H10`) requires additional dependencies. – hyankov Feb 28 '17 at 13:41
  • 1
    @Moses, I have updated the answer to comment about where `lines` comes from. – hyankov Feb 28 '17 at 13:49
  • @Hristo This is great, I really like your approach with the interface. Thanks for your idea and further help using it!!! – Moses Feb 28 '17 at 14:07