Reputation: 63
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
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
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
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