So, I have some code that I am working on (not a dev, but we deal with the hand that we have been dealt) and I have a small, but frustrating error that is popping up, after I have struggled my way through the rest of the issues. In this code:
public class ObservableCollection<ReportData>
{
public string GetQueryData(string date, string ip, string query)
{
ObservableCollection<ReportData> result = new ObservableCollection<ReportData>();
try
{
var data = GetRemoteQueryJournal(ip, date, query);
if (data != null)
{
result = GetReporData(data);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException.StackTrace);
}
return result;
}
}
If the class only gets strings, shouldn't the return BE a string? But I get
A C# (or any programming language) function has a standard definition, which looks like this:
return_type FunctionName (parameter1_type parameter1_name, parameter2_type parameter2_name,...)
{
}
Parameters are optional
Return type can be void, in which case you don't need to return anything.
For all other return types, you need to return a value matching the return type.
So let's take a very simple function, which takes in two ints as parameters, adds them up, and return the sum as an int. It would look like this:
int AddTwoInts(int a, int b)
{
int sum = a + b;
return sum;
}
The return type is int, and the sum is also int so it works fine.
Say you want to add two ints but return the sum as a string for whatever the reason. Then your function definition would have to look like this:
string AddTwoIntsAndReturnAsString(int a, int b)
{
// function body
}
However, in this case, if you had the same function body as before, the compiler will complain:
string AddTwoIntsAndReturnAsString(int a, int b)
{
int sum = a + b;
return sum;
}
Because sum is still and int but the function wants you to return a string, which is a conflict and the compiler doesn't like that. So it will complain. You can fix this by matching what you return to actual return type.
static string AddTwoIntsAndReturnAsString(int a, int b)
{
int sum = a + b;
return sum.ToString();
}
Now this will get rid of the error, but doesn't necessarily mean that'll fix all problems. Typically if you add two numbers you want the result to be a numeric type where you can do additional calculations later on. So for a function that adds two numbers it makes more sense to actually return an int, not a string.
Similarly in your case, your result is of type ObservableCollection<ReportData>. An ObservableCollection is essentially a list. So converting it into a string and returning just to get rid of the error doesn't make sense, as the function wants to return the above type in order to get some work done later on.
So it'll make more sense to change your function's return type:
public ObservableCollection<ReportData> GetQueryData(string date, string ip, string query)
{
ObservableCollection<ReportData> result = new ObservableCollection<ReportData>();
try
{
var data = GetRemoteQueryJournal(ip, date, query);
if (data != null)
{
result = GetReporData(data);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException.StackTrace);
}
return result;
}
Try this in your return statement
return result.ToString();
or maybe you want
return result != null ? "It worked":"It failed";
That should make the issue clear. You say this function returns a string but you are returning a different object type.
You will have to change the return type from string to ObservableCollection<ReportData> or ReportData (depending on your needs).
If you do this and the class only gets strings as you phrased it, then yes the result will be strings. This is how generics works.
Related
I have many methods which are very similar as shown in the code below:
public static void ReadFromKeyboard(string label, out int retVal)
{
try
{
Console.Write(label);
retVal = int.Parse(Console.ReadLine());
}
catch (Exception)
{
Console.WriteLine("Please insert int value.");
ReadFromKeyboard(label, out retVal);
}
}
public static void ReadFromKeyboard(string label, out float retVal)
{
try
{
Console.Write(label);
retVal = float.Parse(Console.ReadLine());
}
catch (Exception)
{
Console.WriteLine("Please insert float value.");
ReadFromKeyboard(label, out retVal);
}
}
public static void ReadFromKeyboard(string label, out double retVal)
{
try
{
Console.Write(label);
retVal = double.Parse(Console.ReadLine());
}
catch (Exception)
{
Console.WriteLine("Please insert double value.");
ReadFromKeyboard(label, out retVal);
}
}
By the other hand, I don't know which method I will call. I'll discorver it only at runtime.
Is there any way I could rewrite these many methods into a single method named something like "ReadFromKeyboard" which returns either an int, a float or a double depending on the type which is passed to it as a parameter?
Thank you!
As other answers have shown, you can eliminate the duplicated code by a variety of techniques, all of which are horrible and you should not do them.
In particular, do not attempt to use generics to solve this "problem". Generics are for situations where the code is generic. That is why they are called generics! That is, the code operates the same on every possible type. Your example is the opposite of generic code; you have different rules for a small number of types, and the way to handle that situation is to do exactly what you have already done: implement one method per different rule.
I say "problem" in quotes because you do not actually have a problem to solve here, so stop trying to solve it. Writing half a dozen similar short methods is not a major burden on authors or maintainers.
Now, that said, your code is also not as good as it could be and you should rewrite it. The correct way to write your code is:
public static int ReadInteger(string label)
{
while(true)
{
int value;
Console.Write(label);
string read = Console.ReadLine();
bool success = int.TryParse(read, out value);
if (success)
return value;
Console.WriteLine("Please type an integer value.");
}
}
The problems with your original implementation are:
Do not use exception handling as mainline control flow. Do not catch an exception if the exception can be avoided. That's what TryParse is for.
Do not use recursion as unbounded looping. If you want an unbounded loop, that's what while(true) is for. Remember, C# is not tail recursive by default!
Do not use out parameters without need. The method logically returns an integer, so actually return an integer. Rename it so that you do not get collisions with other read methods. There is no compelling benefit to making the caller write Read<int> over ReadInteger, and many compelling benefits to avoiding the out param.
I've tried to implement the code according to Eric Lippert recipes. The code below
does not use exception handling as mainline control flow
does not use recursion at all
does not use output parameters without need
.
private static void Main(string[] args)
{
int intValue = ReadFromKeyboardInt32("enter int");
float floatValue = ReadFromKeyboardSingle("enter float");
double doubleValue = ReadFromKeyboardDouble("enter double");
Console.WriteLine($"{intValue}, {floatValue}, {doubleValue}");
}
public static Double ReadFromKeyboardDouble(string label) =>
ReadFromKeyboard(label, (text) => (Double.TryParse(text, out var value), value));
public static Int32 ReadFromKeyboardInt32(string label) =>
ReadFromKeyboard(label, (text) => (Int32.TryParse(text, out var value), value));
public static Single ReadFromKeyboardSingle(string label) =>
ReadFromKeyboard(label, (text) => (Single.TryParse(text, out var value), value));
public static T ReadFromKeyboard<T>(string label, Func<string, (bool, T)> tryParse)
{
for (; ; )
{
Console.Write($"{label}: ");
var result = tryParse(Console.ReadLine());
if (result.Item1)
{
return result.Item2;
}
Console.WriteLine($"Please enter valid {typeof(T).Name} value");
}
}
Instead of listing all the possible types (which you might not know beforehand), it is possible to use the System.Convert class, specially the Convert.ChangeType() method. As a proof of concept you can use a method like this:
public static void ReadFromKeyboard<T>(string label, out T result) {
Type targetType = typeof(T);
Console.Write($"{label}: ");
string input = Console.ReadLine();
object convertedValue = Convert.ChangeType(input, targetType);
result = (T)convertedValue;
}
You can use this method like this:
public static void Main(string[] args) {
ReadFromKeyboard("enter a double", out double d);
ReadFromKeyboard("enter an int", out int i);
Console.WriteLine($"double: {d}");
Console.WriteLine($"int: {i}");
}
This way it is possible to use any type you want (assuming it is supported by the Convert class). Obviously you can add exception handling and a do-while loop in the ReadFromKeyboard method if you like.
If you want to rely on overload resolution for the runtime to decide which method to call, then you must have a separate method for each type you will support. That's how it works.
On the other hand, if you can allow the user to supply at least a little type information, we can improve things a bit with generics by removing try/catch and using a real return statement. You'd call it like this:
var myNumber = ReadFromKeyboard<double>("Enter a double: ");
And the code would look like this:
public static T ReadFromKeyboard<T>(string label, int maxRetries = int.MaxValue)
{
while (maxRetries >= 0)
{
Console.Write(label);
if (typeof(T) == typeof(int))
{
int result;
if (int.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
}
if (typeof(T) == typeof(float))
{
float result;
if (float.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
}
else if (typeof(T) == typeof(double))
{
double result;
if (double.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
}
else if (typeof(T) == typeof(decimal))
{
decimal result;
if (decimal.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
}
else
throw new InvalidOperationException("Unsupported type");
maxRetries--;
}
throw new InvalidOperationException("Too many bad inputs");
}
But you have to do some really janky casting and type checking to make it work. There is still a potential this can throw an exception, which it seems like you want to avoid, but if your user sits there for more than 2 billion attempts, I doubt they'll be very surprised.
I've enabled the C# 8.0 non-nullable reference types feature in one of my projects, but now I'm unclear about how to represent missing data.
For example, I'm reading a file whose lines are colon-separated key/value pairs. Sometimes there's more than one colon on a line. In that case, the text before the first colon is the key, and the rest is the value. My code to parse each line looks like this:
public (string key, string value) GetKeyValue(string line)
{
var split = line.Split(':');
if (split.Length == 2)
return (split[0].Trim(), split[1].Trim());
else if (split.Length > 2)
{
var joined = string.Join(":", split.ToList().Skip(1));
return (split[0].Trim(), joined.Trim());
}
else
{
Debug.Print($"Couldn't parse this into key/value: {line}");
return (null, null);
}
}
What this does: If we have just one colon, return the key and value. If we have more than one, join the rest of the text after the first colon, then return the key and value. Otherwise we have no colons and can't parse it, so return a null tuple. (Let's assume this last case can reasonably happen; I can't just throw and call it a bad file.)
Obviously that last line gets a nullability warning unless I change the declaration to
public (string? key, string? value) GetKeyValue(string line)
Now in F# I would just use an Option type and in the no-colon case, I'd return None.
But C# doesn't have an Option type. I could return ("", ""), but to me that doesn't seem better than nulls.
In a case like this, what's a good way to say "I didn't find anything" without using nulls?
You could include if the result was successful in parsing by just returning a flag:
public class Result
{
private Result(){}
public bool Successful {get;private set;} = false;
public string Key {get; private set;} = string.Empty;
public string Value {get; private set;} = string.Empty;
public static Successful(string key, string value)
{
return new Result
{
Successful = true,
Key = key,
Value = value
};
}
public static Failed()
{
return new Result();
}
}
public Result GetKeyValue(string line){
return Result.Failed();
}
Then you could use it like
var result = GetKeyValue("yoda");
if(result.Successful)
{
// do something...
}
Alternatiely you could return 2 diffrent types and use pattern matching 👍
Actually, I realize now that part of the problem is that my method is doing two separate things:
Determine whether the line has a key.
Return the key and value.
Thus the return value has to indicate both whether there's a key and value, and what the key and value are.
I can simplify by doing the first item separately:
bool HasKey(string line)
{
var split = line.Split(':');
return split.Length >= 2;
}
Then in the method I posted, if there's no key, I can throw and say that the lines need to be filtered by HasKey first.
Putting on my functional thinking cap, an idiomatic return type would be IEnumerable<(string?,string?)>. The only change to your code would be to change return to yield return, and to remove the return statement if nothing is found.
public IEnumerable<(string? key, string? value)> GetKeyValue(string line)
{
var split = line.Split(':');
if (split.Length == 2)
return (split[0].Trim(), split[1].Trim());
else if (split.Length > 2)
{
var joined = string.Join(":", split.ToList().Skip(1));
yield return (split[0].Trim(), joined.Trim());
}
else
{
Debug.Print($"Couldn't parse this into key/value: {line}");
}
}
The caller then has several options on how to handle the response.
If they want to check if the key was found the old-fashioned eway, do this:
var result = GetKeyValue(line).SingleOrDefault();
if (!result.HasValue) HandleKeyNotFound();
If they prefer to throw an exception if the key is not found, they'd do this:
var result = GetKeyValue(line).Single();
If they just want to be quiet about it they can use ForEach, which will use the key and value if they are found and simply do nothing if they are not:
foreach (var result in GetKeyValue(line)) DoSomething(result.Item1, result.Item2);
Also, for what it's worth, I'd suggest using KeyValuePair instead of a tuple, since it clearly communicates the purpose of the fields.
Input string was not in correct form.
I'm getting an exception on runtime as "System.FormatException".
Follwing lines shows exception-
public int Task
{
get
{
return Int32.Parse(TaskText.Text);
}
set
{
TaskText.Text = value.ToString();
}
}
public int Project
{
get
{
return Int32.Parse(ProjectText.Text);
}
set
{
ProjectText.Text = value.ToString();
}
}
I also tried -
Convert.ToInt32(TaskText.Text)
Convert.ToInt32(ProjectText.Text)
I need to pass these to following constructor,
Harvest_TimeSheetEntry entry = new Harvest_TimeSheetEntry(client,starttime,stoptime,task,project);
this constructor is stored in some class with task and project as integer parameters. And I can't change it because if i changed, it affects other code.
It looks as though you're getting your input from controls accepting user input, which is just asking for failure, since a user can potentially enter something that doesn't represent an integer value. You can use TryParse to avoid this:
var result = 0;
if (int.TryParse(TaskText.Text, out result)) {
return result;
}
return 0;
So, if the value of TaskText.Text == "1", this will succeed; if the value of TaskText.Text == "aaaa", this will fail - and return zero. You example would raise the appropriate exception, as experienced.
However, an exception might be the right thing to happen here, if you can't handle a bad value, don't have an alternative, and the application relies on the input to move forward. More likely, you could do with some validation on your input fields to prevent bad data being submitted.
Since your Harvest_TimeSheetEntry constructor expects task and project to be integers, you must have a list of integers that correspond to the different tasks and projects. Now you can't expect Int32 to know which task corresponds to which number, can you?
I would suggest you use ComboBoxes for TaskText and ProjectText. Then, you can assign the correct corresponding integer to each ComboBoxItem.Tag.
Please note that this goes far beyond the kind of answers you should expect from SO.
if you do not use MVVM or binding you can simply do the check before your need it. t
int task;
int project;
if(!Int32.TryParse(TaskText.Text, out task))
{} //errorhandling here
if(!Int32.TryParse(ProjectText.Text, out project))
{}//errorhandling here
//all fine
var entry = new Harvest_TimeSheetEntry(client,starttime,stoptime,task,project);
You must check if you can parse it into Integer
try
Int32 foo =0;
if (Int32.TryParse(TaskText.Text, out foo))
{
return foo;
}
I have a little problem with a simple console application in which i would like to detect if the user inputs a correctly formatted numerical value.
That is, values such as 1212sss or anything like asjkq12323 or a single character is not accepted. I would like to only accept pure integer values.
Here is what i have tried
bool detectNumber(string s)
{
int value=0;
Int.TryParse(s,out value);
return (value!=0)?true:false;
}
I appreciate any help. Thank you soooo much,,,,,
TryParse returns a boolean. Check that, not the value passed via the out parameter.
if( int.TryParse( s, out value ) )
{
// do something
}
Or just:
return int.TryParse( s, out value );
Incidentally, it is not necessary to initialize a value passed using the out keyword. The method declaring the parameter must initialize it before returning.
int foo; // legal
int.TryParse( "123", out foo );
All BCL "Try" methods follow the same convention (such as double.TryParse() for floating point numbers, as #gdoron mentioned in the comments).
And for the curious, source code for the underlying library which implements int.TryParse().
int value = 0;
bool ok = int.TryParse(s, out value);
return ok;
string line = Console.ReadLine();
int value;
if (int.TryParse(line, out value))
{
Console.WriteLine("Integer here!");
}
else
{
Console.WriteLine("Not an integer!");
}
There are several ways to test for only numeric numbers:
first of all, never use Int because of it's maximum value, either use int or Int32.
Parse
int result;
if (int.TryParse("123", out result))
{
Debug.WriteLine("Valid integer: " + result);
}
else
{
Debug.WriteLine("Not a valid integer");
}
Convert.ToInt32()
// throws ArgumentNullExceptionint
result1 = Int32.Parse(null);
// doesn't throw an exception, returns 0
int result2 = Convert.ToInt32(null);
IsNumeric()
using Microsoft.VisualBasic;
// ......
bool result = Information.IsNumeric("123");
Pattern Matching
string strToTest = "123";
Regex reNum = new Regex(#"^\d+$");
bool isNumeric = reNum.Match(strToTest).Success;
Your code works normal, you can only refactor it a bit. Following code is shorter but does exactly the same:
static bool IsInt32(string s)
{
int value;
return Int32.TryParse(s, out value);
}
I am trying to create a Collection with properties and their respective accessors.
Here is my code:
class SongCollection : List<Song>
{
private string playedCount;
private int totalLength;
public string PlayedCount
{
get
{
foreach (Song s in this)
{
if (s.TimesPlayed > 0)
{
return s.ToString();
}
}
}
}
public int TotalLength
{
get
{
foreach (Song s in this)
{
int total = 0;
total += s.LengthInSeconds;
}
return total;
}
}
}
I'm receiving the error at the "get" point. It tells me that not all code paths return a value... What exactly does this mean, and what am I missing?
Firstly, the reason you're getting that message is that if this is empty, then the code within the foreach block (which is where the required return statement is) would never be executed.
However, your TotalLength() function would always return the length of the first Song, as you're declaring your variable, setting its value, then returning it within the foreach block. Instead, you'd need to do something like this:
int totalLength = 0;
foreach(Song s in this)
{
total += s.LengthInSeconds;
}
return totalLength;
Your PlayedCount function suffers from similar issues (if the collection is empty or contains no elements whose TimesPlayed property is greater than 0, then there would be no way for it to return a value), so judging by your comment you could write it this way:
public int PlayedCount()
{
int total = 0;
foreach(Song s in this)
{
if (s.TimesPlayed > 0)
{
total++;
}
}
return total;
}
It means just as it says, not all code paths return a value.
In this case, if your list is empty, then it cannot call return. In a foreach, there must be at least one item for the code to execute. Now, maybe you know that the list will always contain a value, but the compiler can't know that
What would your method return if this did not evaluate?
if (s.TimesPlayed > 0)
{
return s.ToString();
}
try using an else to return an empty string or something
The fact that 'this' could have no songs- in that case the loops will not execute at all and there is no implicit return value in C#.
Furthermore, your getters don't really make sense unless you only ever had one song in the collection. You need something more like this:
public int TotalLength()
{
get
{
int total = 0;
foreach (Song s in this)
{
total += s.LengthInSeconds;
}
return total;
}
}
Finally, without knowing how you keep track of TimesPlayed for each individual song, I wouldn't know how to implement that getter, but I am sure you can figure it out with this much.