Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method, is it possible to return a StreamReader instance?

I've come across a post where Jon Skeet posted the following:

You can't use any parameterised constructor. You can use a parameterless constructor if you have a "where T : new()" constraint.

I have the following method:

public T ReturnReader<T>(String filePath) where T : TextReader, new()
{
    return new T(filePath);
}

which violates what he mentions above. I need the above method to be able to return both a StreamReader (production code) and a StringReader (unit testing purposes). As far as i can see, streamReader doesnt contain a parameterless constructor so i cant create a parameterless instance and then assign the filePath to it in the calling method.

Anyone see a solution to this?

Thanks for your time!

EDIT: original Methods, A and B

    /// <summary>
    /// Ensures number of columns stated == number of columns in file.
    /// </summary>
    /// <param name="errorMessageList">A running list of all errors encountered.</param>
    public static void ValidateNumberOfColumns(string filePath, int userSpecifiedColumnCount, List<String> errorMessageList)
    {
        int numberOfColumnsInFile = GetNumberOfColumnsInFile(filePath, errorMessageList);

        if (userSpecifiedColumnCount != numberOfColumnsInFile) errorMessageList.Add("Number of columns specified does not match number present in file.");
    }

    public static int GetNumberOfColumnsInFile(string filePath, List<String> errorMessageList)
    {
        int numberOfColumns = 0;
        string lineElements = null;

        try
        {
            using (StreamReader columnReader = new StreamReader(filePath))
            {
                lineElements = columnReader.ReadLine();
                string[] columns = lineElements.Split(',');
                numberOfColumns = columns.Length;
            }
            return numberOfColumns;
        }
        catch (Exception ex)
        {
            errorMessageList.Add(ex.Message);
            return -1;
        }
    }
like image 776
Hans Rudel Avatar asked Dec 05 '22 15:12

Hans Rudel


2 Answers

If you have to resort to reflection to make code feasible for unit testing, that's a bit of a smell - only an indication that you may want to reconsider the design, but certainly something to think about.

I would be tempted to abstract this into a separate interface - IPathReader or something similar:

public interface IPathReader
{
    TextReader CreateReader(string path);
}

Then inject that into the class that you're testing - with an implementation using StringReader in testing, and an implementation using StreamReader in production. (I suspect you don't really need it to return different types.)

Note that this is effectively just a Func<string, TextReader> - so you could use that instead of an interface if you wanted.

like image 69
Jon Skeet Avatar answered Dec 10 '22 11:12

Jon Skeet


Activator.CreateInstance it's not good idea because it's too expensive, so you could try this

    StringReader reader = ReturnReader(() => new StringReader(filePath));
    StreamReader streamReader = ReturnReader(() => new StreamReader(filePath));

    private T ReturnReader<T>(Func<T> reader)
        where T : TextReader
    {
        return reader();
    }

EDIT

as per code, I think the best way is to separate NumberOfColumns and getting first row. So the new method NumberOfColumns will not depend from stream readers

like image 45
GSerjo Avatar answered Dec 10 '22 11:12

GSerjo