I'm beginner in programming and I have tried some simple code in C#. [1]: http://i.stack.imgur.com/zLVbz.jpg
This code deals with simple array initialization ,storing and sorting and so on.Its runs as usual in the first try but when I again want to store in the array it throws the exception which I don't understand.
[![Its the exception I'm getting][1]][1]
static void Main(string[] args)
{
int number;
char y;
string[] answer = new string[10];
bool keeprompting = true;
while (keeprompting)
{
Console.WriteLine("Enter the options given below 1.Add students\n 2.View all details\n 3.Sorting\n 4.Exit\n");
int input = Convert.ToInt16(Console.ReadLine());
switch (input) {
case 1:
Console.WriteLine("Enter the Number of Students to be added to the List");
number = Convert.ToInt16(Console.ReadLine()); **exception **
for (int i = 0; i < number; i++) {
answer[i] = Console.ReadLine();
}
break;
case 2:
foreach(var item in answer)
{
Console.WriteLine(item.ToString());
}
break;
case 3:
Array.Sort(answer);
foreach(var item in answer)
{
Console.WriteLine(item.ToString()); **exception **
}
break;
case 4:
Console.WriteLine("Are you sure you want to exit");
Console.WriteLine("1 for Yes and for No");
y = (char) Console.Read();
if (y != 1) {
keeprompting = true;
} else {
keeprompting = false;
}
Console.WriteLine("thank you");
break;
}
}
}
Any and all suggestion are welcome.
1) Possibly FormatException raised here int input = Convert.ToInt16(Console.ReadLine()); because you enter to the Console not '1', '2', etc., but '1.', '2.' and so on. Or may be you use other symbols that cannot be parsed using Convert.ToInt16() method. Value you enter should be in range -32768..32767 and should not contain any space, dot, comma and other symbols except minus sign.
2) The same may happen here number = Convert.ToInt16(Console.ReadLine()); **exception**
3) And here I think you get NullReferenceException:
Array.Sort(answer);
foreach (var item in answer)
{
Console.WriteLine(item.ToString()); **exception**
}
It happens because you have 10 items in your array (string[] answer = new string[10];), but when you insert students you may insert less than 10, so you have for exapmle 3 items initialized, and other set to its default value - null for string. So your array looks like this: { "John", "Jack", "Ben", null, null, ..., null }. A foreach statement later iterates each item including null values, and it tries to call method on a null object, so you get NullReferenceException.
May be you should better use List<string> instead of array to avoid such kind of problems. Call Add() to add items, and Clear() method before you enter students again(to get free of students from previous "session"). And when you later iterate this collection with a foreach loop everything will be ok.
I posted 2 solutions in your other question about this method in this program that accounts for the array size issues in two different ways. Use int.TryParse instead of convert and you either need to resize the array based on user input on the number of students or you need to check for nulls in each iteration of the answer array, skipping nulls. See the example code i provided in your other question.
Related
Sorry if this is an ultra beginner question but, I need to be able to change all field values except the item number by giving its item number not array index. I already have a add and delete feature.
using System;
struct ItemData
{
public int ItemNumber;
public string Description;
public double PricePerItem;
public int QuantityOnHand;
public double OurCostPerItem;
public double ValueOfItem;
}
class Program
{
public static void Main()
{
int invItems = 0;
var items = new ItemData[10];
while (true)
{
Console.Write("1. Add, 2. Change, 3. Delete, 4. List:");
string strx = Console.ReadLine();
var choice = int.Parse(strx);
Console.WriteLine();
switch (choice)
{
This is what I have so far but not sure where to start
case 2: //change items
{
Console.Write("Please enter an item ID No:");
string input = Console.ReadLine();
int changeItemNumber = int.Parse(input);
bool foundItem = false;
for (int x = 0; x < invItems; x++)
{
if (items[x].ItemNumber == changeItemNumber)
{
//code
}
}
if (!foundItem)
{
Console.WriteLine("Item {0} not found", changeItemNumber);
}
break;
}
It's still a little unclear what you're asking so if this is wide of the mark then feel free to let me know.
Start by mapping out the flow of the process and then implement one piece at a time. Here's a basic series of operations you'll want to do, not necessarily in this order:
Input ItemNumber.
Find index of matching record in array.
If not found report error and stop.
Get record from user.
Set ItemNumber in record to entered value.
Store record in array at same index as original.
If you think about the way the rest of the program works you should see that some of those parts - maybe most of them - are already done somewhere in your code. Your add operation probably already has the Get record from user code. Your delete operation probably has Input ItemNumber and Find index of matching record in array done already.
So pull those parts out into methods and reuse them where it makes sense. Anywhere you're writing the same code multiple times with minor changes try to see if you can extract the code into a small method you can call with maybe some parameters. Your "input a number" code for instance - used for operation selection, item number entry and presumably whatever you're doing for delete - can be pulled out to a supporting method like this one:
static int InputNumber(string prompt)
{
Console.Write($"{prompt}: ");
var inputText = Console.ReadLine();
return int.Parse(inputText);
}
Now when you want to change the way you're doing number input you have a central place to make the changes and you don't have to worry about tracking down all the places you wrote the same code.
The same goes for the rest of the pieces. When two operations share some of the same code then pull that code out to supporting methods. Finding the index of an item in the array by some criteria can - and should - be one of those:
// Return array index of matching item or -1 if not found
int ItemIndex(int itemNumber)
{
for (int i = 0; i < invItems; i++)
{
if (items[i].ItemNumber == itemNumber)
return i;
}
return -1;
}
Likewise the code - whatever that looks like - that your add operation uses to get a record from the user.
Once you do that your change item case looks a lot simpler:
case 2: // change items
var itemNumber = InputNumber("Please enter an item ID");
var index = ItemIndex(itemNumber);
if (index == -1)
{
Console.WriteLine($"Item {itemNumber} not found.");
}
else
{
var newItem = InputItemData();
newItem.ItemNumber = itemNumber;
items[index] = newItem;
}
break;
For bonus points you can actually provide the current values to the InputItemData method so the user can re-use them instead of just asking for a complete new record each time.
I'm now done with the basics of my code, and it works like it should. But now i want to add a try-catch exception so that if the user put is anything else than integers, it will throw an exception and say something like: Wrong input try again. Here is where i need it:
for (int i = 0; i < nummer.Length; i++)
{
Console.Write("Nummer " + talnr + ": ");
talnr++;
string str = Console.ReadLine();
int element = Convert.ToInt32(str);
nummer[i] = element;
}
The loop will loop 10 times and store the inputs in an array. When i try it either makes an exception but contiues the loop or breaks the loop and goes on to the next block of code..
I would favour the use of...
bool parsed = Int.TryParse(str, out myInt);
If (parsed)
{
// do something
}
IMHO, a try/catch block should only really be used when there is a possibility on an unhandled exception (e.g. something volatile such as filesystem interaction) that cannot be "predicted" so as to handle accordingly (e.g. log errors etc.) and then continue without crashing your program.
Always try and handle a "known" with the methods and functions available in the framework to do so.
What you're trying to do doesn't require a try-catch. You can use the TryParse method to check whether the desired input is a properly formed integer, and prompt the user to enter a different input.
for (int i = 0; i < nummer.Length; i++)
{
bool isAnInteger = false;
int element = 0;
Console.Write("Nummer " + talnr + ": ");
talnr++;
string str = Console.ReadLine();
// evaluates to true if str can be parsed as an int, false otherwise
// and outputs the parsed int to element
isAnInteger = int.TryParse(str, out element);
while (!isAnInteger)
{
Console.Write("Wrong input, try again. ");
str = Console.ReadLine();
isAnInteger = int.TryParse(str, out element);
}
nummer[i] = element;
}
Use the int.TryParse method.
This code reads the trimmed input string from the user and tries to convert it to an integer. If the conversion is successful, it creates an integer variable called "result" which you can then use in the IF block. If the conversion fails, you can write the code for what you want to happen in the ELSE block. If you need a total of 10 integers in the list, I would drop the FOR loop in favor of a DO WHILE loop that checks for how many integers were successfully converted and added to the list. It will keep requesting input until the list is filled with 10 integers.
List<int> elements = new List<int>();
do
{
Console.WriteLine("Please enter an integer.");
if (int.TryParse(Console.ReadLine().Trim(), out int result))
{
Console.WriteLine($"You entered the integer {result}");
elements.Add(result);
}
else
{
Console.WriteLine("You must enter an integer. Please try again.");
}
} while (elements.Count < 10);
If you want to keep your code with the try catch loop here it is:
for (int i = 0; i < nummer.Length; i++)
{
try {
Console.Write("Nummer " + talnr + ": ");
talnr++;
string str = Console.ReadLine();
int element = Convert.ToInt32(str);
nummer[i] = element;
}
catch
{
MessageBox.Show("Error, numbers only");
goto breakloop;
}
}
breakloop:;
The goto statement ends the loop if an error occured
It's a good idea to not throw exceptions at all if you can help it. In this case, you can use int.TryParse() instead. This block of code can replace your one int element... line:
int element;
if (!int.TryParse(str, out element)) {
Console.WriteLine("Bad!!");
i--;
continue;
}
The i-- is to make sure that i has the same value on the next interaction of the loop. Doing that will make sure you still get 10 valid inputs before you finish the loop. (Many will say this is a really bad thing to do, but the reason for that is readability - if you have a large for loop and decrement the value somewhere in the middle, it makes it difficult for the next guy looking at your code to understand exactly what's going on. But this is a short loop, so it's pretty obvious. Just be aware of this.)
The continue keyword skips the rest of that iteration of the loop and moves on to the next (so you don't add the bad value to your array).
I have a battleship like terminal game, the user enters a coordinate like e2, and the program checks one of the instance variables of my object Box, it checks whether hasShip is true, if its true then it will make the coordinate e2 false, and give the output "Ship destroyed"
The problem is that all my objects are called a1,a2,a3,a4,a5,b1,b2 and so on.
I have created 25 instances of the Box class. All names as such.
Once the program gets input, either e4 ,e5 etc. I want to convert that string into an object.
For example( I want to do something like this )
target = Console.ReadLine();
target.hasShip == true;
I want to convert target into an object, then use target to use the methods of the Box class.
Because the other approach requires me to make loads of if statements, which isn't clean code, doesn't look good, and is a waste if you ask me.
Thanks in advance,
New Answer: use an Array
I am slow. I did not pay attention that you are making a battleship-like game, and that we know that the "boxes" make a rectangle. We can store this efficiently in an array.
Why I did not catch up to this fact earlier? I guess I need to wake up properly.
So, use an array:
var board = new Box[5, 5];
Now, to populate it, we can do a double for loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
board[indexRow, indexCol] = new Box();
}
}
Note: pay attention that the indexes go from 0 to 4. For a total of 5 values: {0, 1, 2, 3, 5}.
And to query from it, we will need the indexes...
Addendum on populating the array
In comments, OP has said that each Box has an id and the ship positions are picked at random.
We can give the id in the loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
var box = new Box();
box.vhID = (((char)(((int)'a') + indexRow))).ToString() + ((char)(((int)'1') + indexCol)).ToString();
board[indexRow, indexCol] = box;
}
}
What I am doing here is constructing the id from the indexes. Basically taking the value of 'a' and adding the indexRow will give us 'a' when indexRow is 0, 'b' when it is 1 and so on. Similarly, we get the digit that represents the column.
Note: We convert the char to int, do the addition, then convert back to char... and then from char to string. Once we have string, we can concatenate them.
I do not think we need this id. But, hey, you can do it like this.
OP also mentions that he will pick 4 ship positions at random. Fair enough:
var random = new Random();
for (var ships = 0; ships < 4; ships++)
{
board[random.Next(0, 4), random.Next(0, 4)].hasShip = true;
}
Since the user inputs an string, I suggest to create a function to convert it to the index pair:
var input = Console.ReadLine();
if (TryGetCoordinates(input, out int irow, out int icol))
{
var target = board[irow, icol];
}
else
{
Console.WriteLine("The cell {0} does not exist.", input);
}
// ...
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
// ...
}
Start by validating null:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
// ...
}
Note: Feel free to use Trim, ToUpper or ToUpperInvariant.
We know that must be a letter followed by a digit, we can validate the length:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
// ...
}
We extract the characters and from them the coordinates. Noting that the first one is a letter, and the other a digit. We can also validate they are withing bounds.
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
indexRow = (int)cell[0] - (int)'a';
indexCol = (int)cell[1] - (int)'1';
return indexRow < 5 && indexRow >= 0 && indexCol < 5 && indexCol >= 0;
}
And of course, you can do a loop of the validation similar to what was explained in the old answer.
Note: the issue with value types I describe in the old answer still applies with the array.
Old Answer: Use a Dictionary
I believe you do not want to convert the string to an object (the string is an object by the way), you want to pick the Box object you previously created based on the string. And you want to do it without using if statements. What you need is a dictionary.
So, you would have Dictionary<string, Box> meaning that it is a dictionary that you can query by string and stores Box.
Addendums:
In this case, string is the key type, by which we will access the dictionary. When we add an object to the dictionary we identify it with a key, and when we retrieve it, we also use the key. The key does not have to be string, you can choose a different type. string is convenient in this case because it is what you get from Console.ReadLine().
You can create the dictionary to store whatever type you need. If you do not need Box, you can create a dictionary that stores something else.
Creating and populating the Dictionary
Then, you add to the Dictionary all your Box objects, like this:
var dict = new Dictionary<string, Box>();
// ...
dict.Add("a1", CreateBoxA1());
Where CreateBoxA1 represents whatever means you have to create the object. No, you do not need to create a method for each Box... you can do it like this:
dict.Add("a1", new Box());
Or whatever. I do not know how you create them, so consider that a placeholder, ok? ok.
Querying and retrieving values from the Dictionary
Once you have all your Box instances in your dictionary, you can get the one you need using the string:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
var target = dict[name];
Addendum: The value you get from dict[name] is the value that you added to the dictionary with that key. So, if the user typed "a1" it dict[name] will be the value that we added with "a1" (dict.Add("a1", new Box());). Again, if what you need is not Box you can create a dictionary to store a different type.
Input validation
You can also use the Dictionary to validate if the string corresponds to a Box that exists, for example:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
var target = dict[name];
// ...
}
else
{
Console.WriteLine("The Box {0} does not exist", name);
}
It goes without saying, but... you can make a loop based on that, for example:
Box target = null;
while(true)
{
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
target = dict[name];
break;
}
Console.WriteLine("The Box {0} does not exist", name);
}
Also, it goes without saying, but... you can add your own validations and sanitation steps. For example using ToUpper, ToUpperInvariant or Trim. And I would remind you that changing strings to lower or upper case is culture sensitive.
See also: Best Practices for Using Strings in .NET.
Editing an removing objects from the dictionary
Once you have the object you retrieved from the Dictionary...
var target = dict[name];
We can use it, and even modify it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
Console.WriteLine("Ship Destroyed");
}
An special note must be done if Box is value type. For a custom type that means that it is not a class but a struct. The problem with value types is that they are copied on assignment, meaning that when you do var target = dict[name]; with a value type, you get a copy. You must then update the dictionary once you manipulated it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
dict[name] = target;
Console.WriteLine("Ship Destroyed");
}
Note: As I said above, this is only needed for value types.
And you can even remove the Box from the dictionary if that is necesary:
dict.Remove(name);
I have a simple program that is used to pass inputs to an array given the inputted size and inputted elements by the user as follows:
Full code
All the code works fine except this snippet :
for(int i=0; i<size; i++) {
flagger = false;
while(flagger == false) {
System.Console.WriteLine("Please enter number " + i + " : ");
nextInput = System.Console.ReadLine();
flagger = errorCheck(nextInput);
}
int varPass = System.Convert.ToInt32(nextInput);
System.Console.WriteLine(flagger);
arr[i] = varPass;
}
No matter what size I input the loop exits early and sorts an array with some empty elements, can anyone see the problem?
In errorCheck you're setting the value of size as you're passing it in the out parameter.
So the next time the user inputs a number smaller than i the loop exits early.
Create a dummy int or handle your error checking differently.
Example:
static bool errorCheck(string input) {
int temp=0;
if (int.TryParse(input, out temp))
return true;
return false;
}
This "bug" happens because size is static.
You can simplify your code further by just returning the result of TryParse.
static bool checkErr(string input) {
int temp=0;
return int.TryParse(input, out temp);
}
You haven't included errorCheck method which seems crucial to me.
If errorCheck returns true the Loop WILL end prematurely.
EDIT: Oops, you did include a link to full code.
As my predecessor said, errorCheck(...) modifies "size". Maybe that's why I still prefix member variables with underscore. It's so easy to miss.
I have trouble implementing the Y/N or y/n in the loop. I've designed it in a way that a user can use both the capital and small letters of the Y and N for their answer in a loop. by the way here's my code but can't seem to make it work:
do
{
Console.WriteLine("\nSelect additional topping/s\n");
Console.WriteLine("1 - Extra meat: 200");
Console.WriteLine("2 - Extra cheese: 100");
Console.WriteLine("3 - Extra veggies: 80\n");
int selectedTopping = Convert.ToInt32(Console.ReadLine());
switch (selectedTopping)
{
case 1:
pizza = new MeatToppings(pizza);
break;
case 2:
pizza = new CheeseToppings(pizza);
break;
case 3:
pizza = new VeggieToppings(pizza);
break;
default:
break;
}
Console.WriteLine("\nAdd more toppings? Y/N");
}
while ((Console.ReadLine() == "Y") || (Console.ReadLine() == "y"));
while ((Console.ReadLine() == "Y") || (Console.ReadLine() == "y"));
This is going to read 2 different lines since you're calling ReadLine() twice. You need to call it once and save the value.
You can use ToUpper
while ((Console.ReadLine().ToUpper() == "Y") );
Try to use String.Equals and StringComparison:
String.Equals(Console.ReadLine(), "y", StringComparison.CurrentCultureIgnoreCase);
from MSDN:
CurrentCultureIgnoreCase: Compare strings using culture-sensitive sort rules, the current culture, and ignoring the case of the strings being compared.
OrdinalIgnoreCase: Compare strings using ordinal sort rules and ignoring the case of the strings being compared.
To check Y or y ignoring case, you should use string.Equals(string,StringComparison) overload.
while (Console.ReadLine().Equals("Y", StringComparison.InvariantCultureIgnoreCase));
Please see the The Turkish İ Problem and Why You Should Care before using ToUpper or ToLower for string comparison with ignore case.
Your current code is reading the lines from console twice, that is why your code is holding up for the 2nd value.
As Austin just pointed out, you are using ReadLine twice in the while loop statement.
One thing worth mentioning is try to follow the rule of modularity, it will help speed up implementing and debugging our code.
It's been a while since I did any C# programming so sudo-coding this in Java style.
Since it's a command line programming you are probably have to validate user input more than once. One thing I would do is make a utility class to contains common user input tasks.
public class TerminalUtil {
private TerminalUtil() {}
public static boolean isYes(String msg){ return (msg.ToUpper() == "Y" || msg.ToUpper() == "YES"); }
public static boolean isNo(String msg){ return (msg.ToUpper() == "N" || msg.ToUpper() == "NO"); }
// You also might want basic conditionals to check if string is numeric or contains letters.
// I like using recursion for command line utilities so having a method that can re-print messages is handy
public static void display(String[] messages){
for(String msg : messages){
Console.WriteLine(msg);
}
}
public static boolean enterYesOrNo(String[] messages, String[] errorMessages){
display(messages)
String input = Console.ReadLine();
if( isYes(input) ){
return true;
} else if( isNo(input) ){
return false;
} else {
display(errorMessages); // Maybe something like, you didn't enter a yes or no value.
enterYesOrNo(messages, errorMessages); // Recursive loop to try again.
}
}
}
Here is what the code to order a pizza might look like
public class OrderPizza{
public static int selectToppings(){
String[] message = new String[4];
message[0] = ("\nSelect additional topping/s\n");
message[1] = ("1 - Extra meat: 200");
message[2] = ("2 - Extra cheese: 100");
message[3] = ("3 - Extra veggies: 80\n");
int option = TerminalUtils.entryNumeric(message, {"You entered an non-numeric character, try again"} );
if( option > 0 && option <= 3 ){
return option;
} else {
Console.WriteLine("Number must be between 1 - 3, try again.");
return selectToppings();
}
}
public static Pizza order(){
Pizza pizza = new Pizza();
while(true){
int toppingCode = selectTopping();
pizza.addTopping(toppingCode);
if(!TerminalUtil.enterYesOrNo({"\nAdd more toppings? Y/N"}, {"Please enter a 'Y'es or 'N'o"}) ){
break;
}
}
}
}
The main benefit of this is that the business logic of the while loop has been reduced and you can reuse the code in TerminalUtils. Also this is by no mean a elegant solution, I'm lazy and it's 3am IRL, but it's should be enough to the ball rolling.
One thing you should probably reconsider doing is using integer codes to represent toppings. Using an enum might make things easier to implement.
I also notice that you add three different types of pizza's, which I'm assuming three separate objects.
Since you are looping to add toppings to a pizza, make an abstract class of pizza. This way you could extend it generic pre-built pizzas such as pepperoni or cheese and use the abstract pizza class if you want the customer to customize their order.
I didn't found better way than:
while ( str!="N" )
{
str = Console.ReadLine();
str = str.ToUpper();
if (str == "Y");
break;
};