1

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?

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
user2795947
  • 53
  • 1
  • 5
  • 3
    You can't "bypass" it. The code has to be written so it doesn't call ReadKey() when input is redirected. Use the [IsInputRedirected method](http://stackoverflow.com/a/3453272/17034). Or instruct the students to not use ReadKey(). Or ReadLine() for that matter. Teaching them how to set a breakpoint at the last brace of Main() so they don't need to do that would be useful. – Hans Passant Nov 12 '16 at 16:42
  • Use a Teensy (or anything else) to emulate a keyboard that "types" your content. Then you won't need to redirect input, so ReadKey will work! – wablab Nov 12 '16 at 16:56
  • Did you ever figure out a programmatic way of handling this? – arao6 Nov 14 '22 at 14:37

2 Answers2

2

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.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
  • Thanks a lot for your well . Although your answer is very well explained and is the "correct" way to do this - I can not use it due to the pedagog complexity of explaining the students on interfaces & depedencies - they are just "hello World" freshmen... – user2795947 Dec 04 '16 at 22:10
0

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.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47