0

I would like to convert this (which is error prone)....

    public static void GenerateReport(string report)
    {
        switch (report)
        {
            case "ReportA":
                // do stuff
                break;
            case "ReportB":
                // do stuff
                break;
            case "ReportC":
                // do stuff
                break;
        }
    }

To this....

    public static void GenerateReport<T>()
    {
        switch (T) // BUT.... how do I handle this?
        {
            case ReportA:
                // do stuff
                break;
            case ReportB:
                // do stuff
                break;
            case ReportC:
                // do stuff
                break;
        }
    }

I have seen a LOT of questions that ask almost the same thing, but none of them have led me to an answer. Like this one, but for me, the solution provided in that thread flat out doesn't work. It throws multiple syntax errors when I try to compile. the solution there says:

switch typeof(T) { 
    // 
}
Community
  • 1
  • 1
Casey Crookston
  • 13,016
  • 24
  • 107
  • 193
  • 3
    Make the Type in question handle the "do stuff" part. Basically change your `switch` into OOP design. – juharr Oct 07 '16 at 14:46
  • 1
    Not sure I follow – Casey Crookston Oct 07 '16 at 14:47
  • 1
    This is basically polymorphism. Make an abstract `Report` class that has a `GenerateReport()` method. –  Oct 07 '16 at 14:47
  • 1
    http://refactoring.com/catalog/replaceConditionalWithPolymorphism.html – David Oct 07 '16 at 14:47
  • You have to wait for [C# 7.0 Pattern Matching](https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/) if this question is about the switch/case using types. – CSharpie Oct 07 '16 at 14:49

4 Answers4

3

You don't need generics for that, nor a switch statement with type detecting... instead use method overloading for each of the types and keep the do stuff pieces in their own methods...

public static void GenerateReport(ReportA a) { /*do stuff*/ }
public static void GenerateReport(ReportB b) { /*do stuff*/ }
public static void GenerateReport(ReportC c) { /*do stuff*/ }
Rudu
  • 15,682
  • 4
  • 47
  • 63
1

The key point about generics is that if the thing you are doing isn't roughly the same for each final T, then it isn't actually generic and you shouldn't be doing it that way.

Good candidates here might include:

  • an enum
  • polymorphism (a virtual / abstract method)
  • passing in a Type instance

but... not generics. The reason the language isn't helping you is because this isn't a good fit. That said: you could do:

if(typeof(T) == typeof(Foo)) {
    // foo
} else if (typeof(T) == typeof(Bar)) {
    // bar
} ...

but; that is kinda missing the point of generics.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
1

Whenever you have if/switch statements where the code will execute differently depending on the input but produce generic output like what you have in your question it is usually a sign that you need to look into doing some refactoring.

In this case the best option would be to use an interface based design and move the logic for executing the various reports into their own types. This will allow you to better manage additional reports on an as needed basis without having to touch the existing code.

public interface IReporter {
    void GenerateReport();
}

public class ReporterA : IReporter {
    public void GenerateReport() { /* execute report */} 
}
public class ReporterB : IReporter {
    public void GenerateReport() { /* execute report */} 
}
public class ReporterC : IReporter {
    public void GenerateReport() { /* execute report */} 
}

// The responsibilty of the factory is only to create the correct reporter based on the request
public class ReporterFactory{
    public IReporter CreateReporter(string input){
        /*  the logic here can vary, you can get creative with Attributes 
            and name each report type and use reflection to create the 
            correct report type. You can also use an Enum and use that as an attribute value over
            each Reporter type. There are many ways to handle it.
        */
    }
}

/* your refactored method */
public static void GenerateReport(string report)
{
    /* again, creation pattern could be based on something other than a string. It depends on how you want to differentiate your reporters*/
    var reporter = new ReporterFactory().CreateReporter(report);
    reporter.GenerateReport();
}   
Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    Ok, thanks. This turns the refactor into a larger project than I had imagined, but I can see that this is the best way to get this done, for future expansion. I'll have some questions along the way, but I'll post them in a separate thread. – Casey Crookston Oct 07 '16 at 15:26
0

You can do it like this:

    public static void RunIfEqual<TLeft, TRight>(Action action)
    {
        if (typeof(TLeft) == typeof(TRight))
        {
            action();
        }
    }

    ...

    RunIfEqual<T, ReportA>(()=> ...);
    RunIfEqual<T, ReportB>(()=> ...);
    RunIfEqual<T, ReportC>(()=> ...);

Or even better way, you can define some ReportGeneratorFactory, that will choose which generator to use for this type and return it to you. Then you can just call GenerateReport on it.

eocron
  • 6,885
  • 1
  • 21
  • 50