Proper nullable type checking in C#? - c#

Ok, my actual problem was this: I was implementing an IList<T>. When I got to CopyTo(Array array, int index), this was my solution:
void ICollection.CopyTo(Array array, int index)
{
// Bounds checking, etc here.
if (!(array.GetValue(0) is T))
throw new ArgumentException("Cannot cast to this type of Array.");
// Handle copying here.
}
This worked in my original code, and still works. But it has a small flaw, which wasn't exposed till I started building tests for it, specifically this one:
public void CopyToObjectArray()
{
ICollection coll = (ICollection)_list;
string[] testArray = new string[6];
coll.CopyTo(testArray, 2);
}
Now, this test should pass. It throws the ArgumentException about not being able to cast. Why? array[0] == null. The is keyword always returns false when checking a variable that is set to null. Now, this is handy for all sorts of reasons, including avoiding null dereferences, etc. What I finally came up with for my type checking was this:
try
{
T test = (T)array.GetValue(0);
}
catch (InvalidCastException ex)
{
throw new ArgumentException("Cannot cast to this type of Array.", ex);
}
This isn't exactly elegant, but it works... Is there a better way though?

There is a method on Type specifically for this, try:
if(!typeof(T).IsAssignableFrom(array.GetElementType()))

The only way to be sure is with reflection, but 90% of the time you can avoid the cost of that by using array is T[]. Most people are going to pass a properly typed array in, so that will do. But, you should always provide the code to do the reflection check as well, just in case. Here's what my general boiler-plate looks like (note: I wrote this here, from memory, so this might not compile, but it should give the basic idea):
class MyCollection : ICollection<T> {
void ICollection<T>.CopyTo(T[] array, int index) {
// Bounds checking, etc here.
CopyToImpl(array, index);
}
void ICollection.CopyTo(Array array, int index) {
// Bounds checking, etc here.
if (array is T[]) { // quick, avoids reflection, but only works if array is typed as exactly T[]
CopyToImpl((T[])localArray, index);
} else {
Type elementType = array.GetType().GetElementType();
if (!elementType.IsAssignableFrom(typeof(T)) && !typeof(T).IsAssignableFrom(elementType)) {
throw new Exception();
}
CopyToImpl((object[])array, index);
}
}
private void CopyToImpl(object[] array, int index) {
// array will always have a valid type by this point, and the bounds will be checked
// Handle the copying here
}
}
EDIT: Ok, forgot to point something out. A couple answers naively used what, in this code, reads as element.IsAssignableFrom(typeof(T)) only. You should also allow typeof(T).IsAssignableFrom(elementType), as the BCL does, in case a developer knows that all of the values in this specific ICollection are actually of a type S derived from T, and passes an array of type S[]

List<T> uses this:
try
{
Array.Copy(this._items, 0, array, index, this.Count);
}
catch (ArrayTypeMismatchException)
{
//throw exception...
}

Here is a little test of try / catch vs. reflection:
object[] obj = new object[] { };
DateTime start = DateTime.Now;
for (int x = 0; x < 1000; x++)
{
try
{
throw new Exception();
}
catch (Exception ex) { }
}
DateTime end = DateTime.Now;
Console.WriteLine("Try/Catch: " + (end - start).TotalSeconds.ToString());
start = DateTime.Now;
for (int x = 0; x < 1000; x++)
{
bool assignable = typeof(int).IsAssignableFrom(obj.GetType().GetElementType());
}
end = DateTime.Now;
Console.WriteLine("IsAssignableFrom: " + (end - start).TotalSeconds.ToString());
The resulting output in Release mode is:
Try/Catch: 1.7501001
IsAssignableFrom: 0
In debug mode:
Try/Catch: 1.8171039
IsAssignableFrom: 0.0010001
Conclusion, just do the reflection check. It's worth it.

Related

TryParsing from an object list to int | c#

I'm trying to take objects out of an object list to an int list. If the object list's value contains a string than I want to convert it to an int. the error that I'm getting is "cannot convert from 'object' to 'System.ReadOnlySpan'. I've tried looking up examples and information about lists made of objects but couldn't find anything.
I'm also at a loss as to what to do with the 'else' part of the code.
public class ListFilterer
{
public static IEnumerable(int) GetIntegersFromList(List(object) listOfItems)
{
List<int> Integers = new List<int>();
foreach (var value in listOfItems)
{
int number = 0;
bool success = Int32.TryParse(value, out number);
if (success)
{
Integers.Add(number);
}
else
{
Integers.Add(number);
}
}
return Integers;
}
}
It'll probably work out if you TryParse value.ToString() instead, if you're looking for anything that might look like an int and can be converted to an int. If you only want things that actually are ints, something like if(value is int number) should work if your c# version is recent. If it's older you may have to if(value is int) and then cast the value inside the if
Your code can be simplified to:
foreach(...){
int.TryParse(value.ToString(), out var n);
integers.Add(n);
}
Or
foreach(...){
if(value is int)
integers.Add((int)value);
else
integers.Add(0);
}
You could simply use:
var ints = listOfItems
.Select(o => { int.TryParse(o.ToString(), out int num); return num;} )
.ToList();
This will work as you wish, as if conversion fails num is 0 by default.
If Try Parse fails number is automatically 0 so you can directly write this
Int32.TryParse(value, out int number)
Integers.Add(number);
Maybe you can find the better way
var intList = objs.ConvertAll(delegate (object obj) { return (int)obj; });

Stackoverflow only with very large ArrayLists

I'm using a recursive version of the insertion sort algorithm to sort 5000 objects based upon a randomly generated integer property, but I've been getting a stackoverflow exception only at an ArrayList of this size while working fine for ArrayLists of other sizes.
I used Console.WriteLine to see what the "position" integer goes up to in one of my methods and it ends up at `4719 before skipping a line and giving a stackoverflow exception. How should I get around this?
I should also mention that when testing an iterative version of insertion sort in the same Visual Studio solution and using an ArrayList of the same size of objects I do not get a stackoverflow exception.
My code for the recursive insertion sort is below (AL is the ArrayList):
public void IS()
{
ISRM(0);
}
private void ISRM(int position)
{
if (position == AL.Count)
return;
Console.WriteLine(position);
int PositionNext = position + 1;
ISRMNext(position, PositionNext);
ISRM(position + 1);
}
private void ISRMNext(int position, int PositionNext)
{
if ((PositionNext == 0) || (PositionNext == AL.Count))
return;
Webpage EntryNext = (Webpage)AL[PositionNext];
Webpage EntryBefore = (Webpage)AL[PositionNext - 1];
if (EntryBefore.getVisitCount() < EntryNext.getVisitCount())
{
Webpage temp = EntryBefore;
AL[PositionNext - 1] = AL[PositionNext];
AL[PositionNext] = temp;
}
ISRMNext(position, PositionNext - 1);
}
Well, first of all, sorting through recursive call is a bad idea for several reasons.
As you've already found out, this easily leads to a stack overflow due to limited size of the stack.
It will have poor performance by definition since function call and accompanying allocation of local function context on the stack is much more expensive operation compared to something like while or for operators iterating through plain collection.
These are two reasons why #Zer0 probably suggested it, but there's more to it.
There's ready ArrayList.Sort() method waiting for you that takes custom comparator. All you need is to write said comparator for your custom objects according to whatever rules you want and call Sort(your_comparator). That's it. You do not need to re-invent the wheel implementing your own sorting method itself - unless implementing sorting method is the actual goal of your program... but I honestly doubt it.
So, It could be something like this (not tested!):
class MyComparer : IComparer
{
public int Compare(object x, object y)
{
var _x = ((Webpage) x).getVisitCount();
var _y = ((Webpage) y).getVisitCount();
if (_x < _y)
{
return -1;
}
if (_x > _y)
{
return 1;
}
return 0;
}
}
Usage:
var myAL = new ArrayList();
// ... filling up the myAL
myAL.Sort(new MyComparer());

Increase readability in this try-catch?

I was wondering if there is a more aesthetic/easier to read way to write the following:
for (int i = 0; i < 100; i++)
{
// If m.GetString(i) throws an exception, continue.
// Otherwise, do stuff.
try
{
string s = m.GetString(i);
continue;
}
catch (InvalidCastException)
{
}
// do stuff with the message that you know is not a string.
}
Here's what m looks like:
msg[0] = 10
msg[1] = "string"
msg[2] = 2.224574743
// Etc.
// Assume it's different every time.
So therefore when I do m.GetString(0) in this example, it throws an exception, as msg[0] is a uint and not a string. This is what I use to get the type, since m does not contain a GetType and I cannot edit m.
m is an instance of class Message in a library that I cannot edit.
However, despite this working just fine, it feels inefficient (and certainly not reader-friendly) to intentionally create exceptions, even if it's in a try-catch, in order to get the type.
Is there a better way or am I stuck with this?
Edit: Alright, I researched the Message class some more (which I should have done to begin with, my apologies). It is an IEnumerable<object>
Now that I know that m is an IEnumerable<object>, I think this is probably your best bet:
foreach (string s in m.OfType<string>())
{
// Process s, which can't be null.
}
Nice and simple and it appears to handle all the logic that you want, i.e. it will process only the items in the sequence that are strings, and it will ignore all objects of other types.
However as Servy points out, this will not handle nulls in the list because null does not have any type at all.
[My previous answer before I knew the type of m]
I think you can take one of three approaches to this:
(1) Add a bool TryGetString(int index, out string) method to whatever type m is in your example and then do
if (m.TryGetString(i, out s))
// Process s (need to check for null!)
(2) Add a bool IsString(int index) method and call that before calling GetString().
if (m.IsString(i))
{
s = m.GetString(i);
// Process s (need to check for null!)
(3) Alternatively, you could expose the item via something like GetObject(int index) and then do something like Iiya suggested:
string s = m.GetObject(i) as string;
if (s != null)
// Process s
I think (1) or (3) would be best, although there might be a much better solution that we could suggest if we had more information about m.
If you want to process only strings in a non-strongly typed sequence of data, use next code:
for (int i = 0; i < 100; i++)
{
string s = m[i] as string;
if(s != null)
{
}
}

ArgumentNullException in an array that I can't figure out

I have been up half the night and still trying to get this null exception figured out. I have read a few of texts about this issue but none has helped me in any way, to me what the problem is as it should work :/ It just crashes at this piece of code:
Private void UpdateGUI()
{
string selectedItem = cmbDisplayOptions.Items[cmbDisplayOptions.SelectedIndex].ToString();
rdbtReserv.Checked = true;
lstReservations.Items.Clear();
lstReservations.Items.AddRange(m_seatMngr.GetSeatInfoStrings(selectedItem));
}
lstReservations.Items.AddRange(m_seatMngr.GetSeatInfoStrings(selectedItem)); Gives me the ArgumentNullExeption, but to me it should not do that.
the addrange sends string selectedItem to another class:
public string[] GetSeatInfoStrings(string selectedItem)
{
int count = GetNumOfSeats(selectedItem);
if (count <= 0)
{
return null;
}
string[] strSeatInfoStrings = new string[count];
for (int index = 0; index <= m_totNumOfSeats - 1; index++)
{
strSeatInfoStrings[index] = GetSeatInfoAt(index);
}
return strSeatInfoStrings;
}
This int count = GetNumOfSeats(selectedItem); goes to here and returns with an int:
private int GetNumOfSeats(string selectedItem)
{
if (selectedItem == "ReservedSeats")
{
return GetNumReserved();
}
if (selectedItem == "VacantSeats")
{
return GetNumVacant();
}
else
{
return m_totNumOfSeats;
}
}
I have checked the arrayed have the correct number of spaces(60) and that selectedItem has a string(Allseats to start with so it should return m_totnumOfSeats which is an int of 60) But then in the private int GetNumOfSeats something goes wrong and it returns null and...well why?
I can't see the problem.. maybe gone blind by trying to find the issue. Always got outstanding help here and I have learned tons!! So maybe someone can point out all the issues there is in my code.
Thanks a million in advance for any and all advice!
//Regards
Check if your variables are actually initialized and returns correct values.
There are logical errors in the GetSeatInfoStrings methods and the GetNumofSeats method.
Lucky for you the GetNumOfSeats method will always return 60 for you because of the wrong way you compare strings. It's not the right way, so use the Equals method for comparison like
if (selectedItem.Equals("ReservedSeats"))
With that you will get a proper output form GetNumOfSeats(string) method.
The next thing is to fix your looping in the GetSeatInfoStrings method so as to not get an array index out of bounds exception like this.
string[] strSeatInfoStrings = new string[count];
for (int index = 0; index <= count; index++)
{
strSeatInfoStrings[index] = GetSeatInfoAt(index);
}
return strSeatInfoStrings;
Also fix the part where your logic returns a null in the GetSeatInfoStrings method. it should return an empty string array according to your logic as
return new string[0];
That should probably get your methods working. You need to be very careful of what you code before you debug it :-)
Looking at the source code of ObjectCollection, when you call AddRange and pass a null value, you get back the ArgumentNullException.
You could prevent this changing this code
if (count <= 0)
{
return new string[0];
}

Unable to cast from object array to a string array

This is my function.
public Dictionary<string, string> ArrayToDictionaryConverter(object [] objArray)
{
string[] strArray = new string[objArray.Length];
strArray =(string[])objArray;
Dictionary<string, string> dictionary = null;
try
{
dictionary = new Dictionary<string, string>();
for (int iVal = 0; iVal < strArray.Length; )
{
dictionary.Add(strArray[iVal], strArray[iVal + 1]);
iVal += 2;
}
}
catch (Exception ex)
{
}
return dictionary;
}
Getting error :
Unable to cast object of type 'System.Object[]' to type 'System.String[]'.
Why ? is this wrong convention / Casting?
You can't cast an expression to a particular type if it's not actually of that type, or a compatible one (or there's an explicit conversion).
In this case, the array wasn't a string[] - it was an object[]. It could have been a string[], which is why the compiler allowed you to write the cast in the first place - but at execution time the CLR found that it was just an object[].
Do you expect every element in the array to already be a string? If so, you should just cast each element individually. If not, you'll have to add a call to ToString (or some other way of converting each element to a string) - but be careful of null values.
As an aside, empty catch blocks are a really bad idea. What do you really want to happen if something goes wrong? And what kind of errors do you want to handle in what way? Some errors to think about:
What if one of the keys is null?
What if you get duplicate keys?
What if the input array has an odd number of elements?
Finally, I'd suggest putting the i += 2 in the for loop "header" instead of at the end of the block.
Try the Array.ConvertAll method.
See This is working:
public Dictionary ArrayToDictionaryConverter(object [] objArray)
{
string[] strArray = Array.ConvertAll(objArray,Convert.ToString);
Dictionary dictionary = null;
try
{
dictionary = new Dictionary();
for (int iVal = 0; iVal < strArray.Length; )
{
dictionary.Add(strArray[iVal], strArray[iVal + 1]);
iVal += 2;
}
}
catch (Exception ex)
{
}
return dictionary;
}

Categories