SingleOrDefault returns null, but what if I want to assign values to represent the object that wasnt found?
+2
A:
You could roll your own.
public static T SingleOrDefault<T>(this IEnumerable<T> enumerable, T defaultValue) {
if ( 1 != enumerable.Count() ) {
return defaultValue;
}
return enumerable.Single();
}
This can be a bit expensive though because Count() requires you to process the entire collection and can be fairly expensive to run. It would be better to either call Single, catch the InvalidOperationException or roll a IsSingle method
public static bool IsSingle<T>(this IEnumerable<T> enumerable) {
using ( var e = enumerable.GetEnumerator() ) {
return e.MoveNext() && !e.MoveNext();
}
}
public static T SingleOrDefault<T>(this IEnumerable<T> enumerable, T defaultValue) {
if ( !enumerable.IsSingle() ) {
if( enumerable.IsEmpty() ) {
return defaultValue;
}
throw new InvalidOperationException("More than one element");
}
return enumerable.Single();
}
JaredPar
2009-05-04 02:57:56
This would return the default value if the enumerable contained multiple objects. Shouldn't it throw an exception like the standard implementation does?
tvanfosson
2009-05-04 03:03:51
@tvanfosson yeah it should. I was under the impression that SingleOrDefault would return the default value if there was anything other than one element. But it apparently will only do so for no elements. Weird, I will update.
JaredPar
2009-05-04 03:05:12
+1
A:
You could create your own extension methods -- SingleOrNew.
public static class IEnumerableExtensions
{
public static T SingleOrNew<T>( this IEnumerable<T> enumeration, T newValue )
{
T elem = enumeration.SingleOrDefault();
if (elem == null)
{
return newValue;
}
return elem;
}
public static T SingleOrNew<T>( this IEnumerable<T> enumeration, Func<T,bool> predicate, T newValue )
{
T elem = enumeration.SingleOrDefault( predicate );
if (elem == null)
{
return newValue;
}
return elem;
}
}
tvanfosson
2009-05-04 03:09:04
The problem with this approach though is you will return the newValue parameter for a collection which contains a single element with the value null. Consider this expression (new string[] { null }).SingleOrNew("foo");. It would incorrectly return "foo"
JaredPar
2009-05-04 03:16:07
@JaredPar -- I'm willing to live with that. Generally, I don't put nulls in my collections and were I to find one when using a method like this, I'd probably want to replace it with my default value anyway.
tvanfosson
2009-05-04 11:25:32
@tvanfosson, that's a fairly substantial breaking change from the original algorithm that makes a hefty assumption about how people use null.
JaredPar
2009-05-04 14:08:00
The whole idea is that you use this when you want to guarantee that you don't get a null object, but one that's populated with a suitable default. I don't see the problem. Wrap the expected functionality in unit tests so that it's clear to the user what is expected. Add some documentation if necessary. I can't think of a single use case that this would break for me.
tvanfosson
2009-05-04 15:15:10
@tvanfosson, when designing an API you should consider other users before yourself. In this case you're offering what amounts to an overload for an existing type of query yet your fundamentally changing the way in which it searches. This may not break you but it will break someone depending on this behavior. Consider as an example the difference between Hashtable and Dictionary. In v1 it was considered OK to return null for Hashtable because it was thought no one would use null. It turned out that was a very bad assumption to the point the changed the behavior for Dictionary in v2
JaredPar
2009-05-05 00:16:42
+5
A:
?? operator. If the left argument is null, evaluate and return the second argument.
myCollection.SingleOrDefault() ?? new[]{new Item(...)}
This will only work with reference types (or nullables), but it would do what you're looking for very simply.
Joe White
2009-05-04 03:14:39