In my opinion, there shouldn't be a need for this method to begin with. There are very few cases I can think of that would involve passing around a null
IEnumerable
(or List<T>
for that matter). IEnumerable
can still express a lack of things to iterate, so you don't often see IEnumerable
members returning null
. I often see null
returned for IEnumerable
when the better option would have been to fail or more explicitly inform the caller of an invalid operation. Take this Repository
example:
public class Repository<T>
{
public IEnumerable<T> GetItems()
{
bool canConnectToDb = false;
bool recordsReturned = false;
// Case 1:
if(!canConnectToDb)
return Enumerable.Empty<T>();
// Case 2:
if(!canConnectToDb)
return null;
// Case 3:
if(!recordsReturned)
return null;
}
}
In "Case 1", the result is misleading. Sure, no records can be returned, but the reason isn't a lack of records to return. Instead, there is an error case that must be handled and an exception should be thrown. In "Case 2", null
is being used to show an error state, which is misleading. What was the error? How can I differentiate between a null response, and error handling code using try..catch
? In "Case 3", the correct return value would be an empty IEnumerable
, not null
.
Which leads me to your extension methods...Usually checks like these are trying to cover up issues rather than failing early and identifying them. For example, lets apply your extension method to refactor the existing Count()
extension method:
public static int Count<T>(this IEnumerable<T> iterator)
{
if(iterator.IsNullOrEmpty())
return 0;
else
// Iterate and count...
}
You just end up perpetrating the issue to create "safe" code that really only hides the issue (that iterator
shouldn't have been passed in as null to begin with). The actual implementation probably looks more like:
public static int Count<T>(this IEnumerable<T> iterator)
{
if(iterator == null)
throw new ArgumentException("iterator");
// Iterate and count...
}
This allows it to fail early if it's improperly used, and doesn't make it this methods responsibility to make sure it can always execute given poor conditions.
Lastly, if you do plan on using something like your extension methods, you should know that the extension methods that come with the .NET framework already make performance checks similar to your check if the object is an ICollection
. That said, there is no performance check they can do on a custom type that implements IEnumerable
, so if you want the count the only option is to iterate through the whole IEnumerable
. You don't really need the count though, you just need to know if there is anything being returned, which the Any()
extension method does. Instead of iterating through the whole collection, it'll just iterate once and immediately return true if there is anything, which can be a huge performance saver.
A rework of your method:
public static bool IsNullOrEmpty(this IEnumerable iterator)
{
return iterator == null || !iterator.Any();
}
Maybe I could be convinced by seeing a good application of this extension method, but I can't think of one.
Edit - To address the concern of using another library you can't entirely trust, you have some other options:
1) Use the Facade Pattern to create wrapper objects that do perform to your standards.
2) Treat the issue at the call site to these external libraries and not further into your own code. For example:
var items = BadLibrary.GetItems<T>() ?? Enumerable.Empty<T>();
InternalGoodMethod(items);
public static void InternalGoodMethod(IEnumerable<T> items)
{
if(items == null)
throw new ArgumentException("items");
....
.Count
property, you should extendICollection
directly, or else this could fail on otherwise valid collections. Use the.Count()
method in an IEnumerable extension. The second and third methods are a waste, however; particularly the simple inverted one. \$\endgroup\$ICollection
extension might be the better solution actually. Good idea. \$\endgroup\$someList.Count > 0
part. If youfor each
(etc) over an empty list, it will effectively do nothing and skip to the next part of you code. You will obviously need to check if it is null still (C#6 may have a solution to that coming soon as well) \$\endgroup\$Any()
.IEnumerable<T>
does. \$\endgroup\$