Avoiding Range Issues in Common Functions

I come from the Wild Wild West PHP and Javascript, where you can return something from a function. Although I hate the lack of responsibility, I also face a new problem in my quest to keep my code "perfect."

I made this general function to select a random item from a list

public static T PickRandom<T>(this IList<T> list) { Random random = new Random(); int rnd = random.Next(list.Count); return list[rnd]; } 

But I want to protect myself from using it in a list with 0 values. Obviously, I cannot return anything from this function except T, for example, false or -1. I can of course do it

 if(myList.Count > 0) foo = Utilites.PickRandom(myList); 

However, in C # there are so many crazy things that I don’t know about, and for this application that I create, I very often have to select a random item from a list that can constantly decrease in its Count. Is there a better way?

+6
source share
3 answers

The options you have are

 return default(T) 

which will be ambiguous, as this may be a valid list item.

Or you can return something like -1 , as you said, but this is quite related to your code.

Or you can return null , but this can only be done if T is a type with a null value.

In all previous cases, if the caller does not know about this situation, the application may continue with an invalid value, which will lead to unknown consequences .

Therefore, probably the best option is to throw an exception:

 throw new InvalidOperationException(); 

With this approach, you quickly execute , and make sure that nothing happens unexpectedly beyond the intentions of the caller.

Another reason to back up this option. Take, for example, the Linq extension methods. If you call First() , Single() or Last() on an empty list, you will receive an InvalidOperationException message with the message "The sequence contains no elements." Providing your class with behavior similar to rum classes is always good.


I add a side note thanks to Alexey Levenkov's comment in the question. This random generation is not the best approach. Take a look at this question .


Second note. You declare your function as an extension method for IList<T> (you do this with this to the first parameter), but then you name it as a static helper method. The extension method is syntactic sugar, which instead:

 foo = Utilites.PickRandom(myList); 

allows you to do this:

 foo = myList.PickRandom(); 

More on extension methods can be found here .

+8
source

Another alternative is the following pair of overloads instead of the original. At the same time, it should be clear to the subscriber that they should provide a random default value in case if it is impossible to select "from the list".

 public static T PickRandomOrReturnDefault<T>(this IList<T> list, T defaultRandomValue) { if (list == null || list.Count == 0) return defaultRandomValue; Random random = new Random(); int rnd = random.Next(list.Count); return list[rnd]; } public static T PickRandomOrReturnDefault<T>(this IList<T> list, Func<T> createRandomValue) { if (list == null || list.Count == 0) return createRandomValue(); Random random = new Random(); int rnd = random.Next(list.Count); return list[rnd]; } 

Note. You should probably consider creating a random static field for a member of the class, rather than re-creating it again and again. See the answer to this post. Correct "static" Random.Next method in C #?

0
source

Another option is to use the Maybe<T> monad. This is very similar to Nullable<T> , but works with reference types.

 public class Maybe<T> { public readonly static Maybe<T> Nothing = new Maybe<T>(); public T Value { get; private set; } public bool HasValue { get; private set; } public Maybe() { HasValue = false; } public Maybe(T value) { Value = value; HasValue = true; } public static implicit operator Maybe<T>(T v) { return v.ToMaybe(); } } 

Your code might look like this:

 private static Random random = new Random(); public static Maybe<T> PickRandom<T>(this IList<T> list) { var result = Maybe<T>.Nothing; if (list.Any()) { result = list[random.Next(list.Count)].ToMaybe(); } return result; } 

You would use it like:

 var item = list.PickRandom(); if (item.HasValue) { ... } 

Personally, I call my methods, perhaps at the end.

It will be something like this:

 var itemMaybe = list.PickRandomMaybe(); if (itemMaybe.HasValue) { ... } 
0
source

All Articles