Search code examples
c#genericsabstract-class

Is there a way to use an abstract generic class within another abstract class?


The question might not be phrased as well as I'd like, but what I've been trying to do is this:

I have an application for running and displaying the results of problems that I've solved from TopCoder. Since all of those problems follow the same sort of format (take some inputs, do a thing, compare the output to the correct answer) I've tried to gather the common functionality in to an abstract class Problem.

Here's a cut down version:

public abstract class Problem
{
   // Some public fields and tasks exist here
   // Some protected methods for reporting progress exist here
   // Some abstract fields for things like the problem name and the link to the problem page exist here

#region Abstract Methods

protected abstract void RunExamplesOnce(CancellationToken token, IProgress<int> progress = null);

protected abstract void RunExamplesForAverage(CancellationToken token, IProgress<int> progress = null);

#endregion

#region Example Class

protected abstract class Example<T1, T2>
{
    public T1 Inputs;
    public T2 CorrectOutput;

    public Example(T1 inputs, T2 correctOutput)
    {
        this.Inputs = inputs;
        this.CorrectOutput = correctOutput;
    }

    public abstract (bool Pass, long ElapsedMilliseconds) TestExample();
}

#endregion

}

I then create a class for each problem, e.g. TestProblem : Problem and override as appropriate. I also create the approproate TestExample : Example which will specify the types of those inputs and output and create an array of these within TestProblem. This all works so far, but a lot of the functionality in RunExamplesOnce and RunExamplesForAverage will be duplicated every time. These functions will always take Examples, iterate through them running a function that will always return a bool Pass and long ElapsedMilliseconds and update the displayed text with those results.

protected override void RunExamplesOnce(CancellationToken token, IProgress<int> progress = null)
        {
            answer = "";

            List<bool> passesForEachExample = new List<bool>();
            List<long> timesElapsedForEachExample = new List<long>();

            for (int i = 0; i < examples.Length; i++)
            {
                // Stop if cancelled
                if (token.IsCancellationRequested)
                {
                    answer = $"{Name} cancelled\r\n";
                    return;
                }

                // Report Progress
                ReportProgress(i, examples.Length, progress);

                // Perform code
                var (Pass, ElapsedMilliseconds) = examples.ElementAt(i).TestExample();

                answer += $"Example {i}: Pass {Pass} in {ElapsedMilliseconds} ms\r\n";
                passesForEachExample.Add(Pass);
                timesElapsedForEachExample.Add(ElapsedMilliseconds);
            }

            ReportProgress(100, progress);
            answer += $"Overall Pass {passesForEachExample.All(c => c = true)} in {timesElapsedForEachExample.Sum()} ms\r\n\r\n";
        }

The way I see it - that function doesn't care what the inputs or output types are. It just needs to hand an Example to TestExample() which will always be overridden appropriately. Is there some way to define the function in the abstract class Problem using an undefined generic Example<T1, T2> (which doesn't seem to work) or a way to change how I've structured these classes to achieve the same result?


Edit:

Thank you for your answers! I ended up taking Legacy Code's advice, which was perfect for what I wanted to do. If anyone is curious, the code is up on my GitHub now at elysiara/TopCoder.


Solution

  • Add an interface to your example abstract class

    protected abstract class ExampleBase<T1, T2> : IExample
    {
        public T1 Inputs;
        public T2 CorrectOutput;
    
        public Example(T1 inputs, T2 correctOutput)
        {
            this.Inputs = inputs;
            this.CorrectOutput = correctOutput;
        }
    
        public abstract (bool Pass, long ElapsedMilliseconds) TestExample();
    }
    
    public interface IExample
    {
            (bool Pass, long ElapsedMilliseconds) TestExample();
    }
    
    

    Then you have to change the element type of your examples array/list/collection to IExample. After that you can have the RunExamplesOnce method in your Problem base class.