user2795947
user2795947

Reputation: 63

Can I bypass Console.ReadKey() issue when input is redirected?

I am a C# teacher and I wrote some automated HW checker for my students. The students write C# Console Applications. My HW checker is based on input redirection so I can test their code on my own generated input.

The problem is that students sometimes end their program with a Console.ReadKey() instruction (They do so just to make the execution window not close when they ran the program under F5 - Debug). The Console.ReadKey() crashes when ran under input redirection with the following exception:

System.InvalidOperationException: Cannot read keys when either application does not have a console or when console input has been redirected from a file.

Do I have any way to "bypass" this problem (without altering the students code)? Maybe tell Console to ignore ReadKey instructions?

Upvotes: 2

Views: 4445

Answers (3)

bambams
bambams

Reputation: 765

The best answer was provided by [Hans Passant][https://stackoverflow.com/users/17034/hans-passant] as a comment to the original post. I'm just providing his answer so that it's possible to be chosen as the correct answer.

He suggested the students use Console.IsInputRedirected to choose between calling Console.ReadKey() or not. This way the program can behave correctly when input is redirected without crashing. Since Console.ReadKey() crashes the program if input is redirected, it is wrong to invoke it if the program is meant to work with redirected input (which is really any program which reads from standard input, as all programs should support redirection).

This was useful for me as we have a similar mechanism in debug builds of a console application. The other developers would run this from Windows and therefore need the mechanism to keep the window open to read it from. And it's also useful for me when running from Visual Studio. But when invoking from the command line as I often do too, it would prevent chaining of multiple calls because they would stop for user input (and redirecting yes would cause the crash).

Upvotes: 0

Massimiliano Kraus
Massimiliano Kraus

Reputation: 3835

If the executables are in IL, you can create an easy application that uses ILDASM.

The key point is: disassemble the executable with ILDASM into a text file/stream, look for any call to Console.Read and remove it, than recompile it and run.

Upvotes: 0

Massimiliano Kraus
Massimiliano Kraus

Reputation: 3835

I see a clear case for a Dependency Injection pattern.

Let's build a simple example, with Read, ReadLine and WriteLine functionalities polymorphically: your students must write a homework in which a number given in the Console.ReadLine() must be parsed as int and returned to the Console Window.

Usually a student writes something like:

class Program
{
    static void Main(string[] args)
    {
        var stringValue = Console.ReadLine();
        int number;

        if (int.TryParse(stringValue, out number))
            Console.WriteLine($"The double of {number} is {number * 2}");
        else
            Console.WriteLine($"Wrong input! '{stringValue}' is not an integer!");

        Console.Read();
    }
}

Now, instead, create an interface for the Console functionalities:

public interface IOutput
{
    void Read();
    string ReadLine();
    void WriteLine(string text);
}

A student must create a Homework class that wraps all the required homework code, using an IOutput instance in this way:

public class HomeWork
{
    private IOutput _output;

    public HomeWork(IOutput output)
    {
        _output = output;
    }

    public void Run()
    {
        _output.WriteLine("Give me an integer:");

        var stringValue = _output.ReadLine();

        int number;

        if (int.TryParse(stringValue, out number))
            _output.WriteLine($"The double of {number} is {number * 2}");
        else
            _output.WriteLine($"Wrong input! '{stringValue}' is not an integer!");

        _output.Read();
    }
}

The Main becomes:

static void Main(string[] args)
{
    var h = new HomeWork(new ConsoleOutput());
    h.Run();
}

You give them also the ConsoleOutput class:

public class ConsoleOutput : IOutput
{
    public void Read()
    {
        Console.Read();
    }

    public string ReadLine()
    {
        return Console.ReadLine();
    }

    public void WriteLine(string text)
    {
        Console.WriteLine(text);
    }
}

So the use it instead of call directly Console.Read() etc.

The student must pass to you not the entire Application, but only the Homework class.

You can create a test class that use the Homework class with some test implementations of IOutput like the followings:

public abstract class TestOutput : IOutput
{
    public TestOutput()
    {
        Outputs = new List<string>();
    }

    public void Read()
    {
        //do nothing?
    }

    public abstract string ReadLine();

    public void WriteLine(string text)
    {
        Outputs.Add(text);
    }

    public List<string> Outputs { get; set; }
}

public class TestOutputWithAValidNumber : TestOutput
{
    public TestOutputWithAValidNumber(int value)
    {
        Value = value;
    }

    public override string ReadLine()
    {
        return Value.ToString();
    }

    public int Value { get; }
}

public class TestOutputWithNotValidNumber : TestOutput
{
    public TestOutputWithNotValidNumber(string value)
    {
        Value = value;
    }

    public override string ReadLine()
    {
        return Value;
    }

    public string Value { get; }
}

The test class can be something like this:

[TestClass]
public class TestOutputClass
{
    [TestMethod]
    public void TestGoodNumber()
    {
        var testOutput = new TestOutputWithAValidNumber(1234);

        var h = new HomeWork(testOutput);

        h.Run();

        Assert.AreEqual(1234, testOutput.Value);
        Assert.AreEqual("Give me an integer:", testOutput.Outputs[0]);
        Assert.AreEqual("The double of 1234 is 2468", testOutput.Outputs[1]);
    }

    [TestMethod]
    public void TestWrongNumber()
    {
        var testOutput = new TestOutputWithNotValidNumber("foo");

        var h = new HomeWork(testOutput);

        h.Run();

        Assert.AreEqual("foo", testOutput.Value);
        Assert.AreEqual("Give me an integer:", testOutput.Outputs[0]);
        Assert.AreEqual("Wrong input! 'foo' is not an integer!", testOutput.Outputs[1]);
    }
}

If you need only to wrap the Console.Read() method, feel free to simplify all this code, but IMHO I thought that a wider view on this possible solution would have been useful anyway.

Upvotes: 2

Related Questions