2

I have a collection

List<KeyValuePair<string, Details>> x

where

public class Details
{ 
    private int x;
    private int y;

    public Details()
    {
        x = 0;
        y = 0;
    }
    ...
}

I am using LINQ on my collection to return the Details instance where

x.Where(x => x.Key == aString).SingleOrNew().Value

Where .SingleOrNew is defined as

public static T SingleOrNew<T>(this IEnumerable<T> query) where T : new()
{            
    try
    {
       return query.Single();
    }
    catch (InvalidOperationException)
    {
       return new T();
    }
}

So if a KeyValuePair<string, Details> is not found in the list x which satisfies the Where clause a new KeyValuePair<string, Details> is returned.

But the problem is the new KeyValuePair<string, Details> includes a null Details value.

When no match is found from the .Where clause I was wondering if any LINQ (extension) method can be used which would return a new KeyValuePair<string, Details> like SingleOrNew but where the .Value / Details part of the KeyValuePair has been initialised using the default Details parameterless constructor? So that its not null!

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
endorphin
  • 656
  • 2
  • 6
  • 15

2 Answers2

5

Use this extension method instead:

    public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T> createNew) 
    {
        try
        {
            return query.Single();
        }
        catch (InvalidOperationException)
        {
            return createNew();
        }
    }

You might also want to have this, so you don't need the Where() clause:

    public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T,bool> predicate, Func<T> createNew) 
    {
        try
        {
            return query.Single(predicate);
        }
        catch (InvalidOperationException)
        {
            return createNew();
        }
    }

Now, you can specify what a new instance of T should be instead of being limited to the default value of a T, and having the constraint of a public parameterless constructor.

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • you have to add `where T: class` otherwise the ?? operator won't work – Stefan Mar 11 '11 at 18:01
  • @Jason, good catch - I updated the code. The class constraint obviously doesn't work for a KeyValuePair, so this should suit the OP's needs. – smartcaveman Mar 11 '11 at 18:15
  • @Jason This is true, but using the `SingleOrNew` definition in the OP but replacing `return new T()` with `return createNew()` has worked a charm. – endorphin Mar 11 '11 at 18:18
  • @Jason, sure. I guess we could do an `query.Any() ? query.Single() : createNew()`, but that seems like it would be slower, right? – smartcaveman Mar 11 '11 at 18:20
  • @smartcaveman, slightly, one could also check if query.SingleOrDefault() equals default(T) – Stefan Mar 11 '11 at 18:27
  • @Jason, I considered that, but its actually wrong. Imagine there is an `IEnumerable` and I am interested in 0 values, but my `createNew == ()=>1`. – smartcaveman Mar 11 '11 at 18:33
1

Actually I'd do it this way

public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T> createNew)
{
    using (var enumerator = query.GetEnumerator())
    {
        if (enumerator.MoveNext() && !enumerator.MoveNext())
        {
            return enumerator.Current;
        }
        else
        {
            return createNew();
        }
    }
}

I don't like testing for a scenario by catching an exception.

Explanation:

First off I'm getting the Enumerator for the Enumerable. The Enumerator is an object that has a MoveNext method and a Current property. The MoveNext method will set Current to the next value in the Enumeration (if there is one) and it returns true if there was another value to move to and false if there wasn't one. Additionally the enumerator is wrapped inside a using statment to make sure that it is disposed of once we are done with it.

So, after I get the enumerator I call MoveNext. If that returns false then the enumerable was empty and we will go straight to the else statement (bypassing the second MoveNext method because of short circuit evaluation) and return the result of createNew. If that first call returns true then we need to call MoveNext again to make sure there isn't a second value (because we want there to be a Single value). If the second call returns true then again it will go to the else statement and return the result of createNew. Now if the Enumerable does have only one value then the first call to MoveNext will return true and the second will return false and we will return the Current value that was set by the first call to MoveNext.

You can modify this to be a FirstOrNew by removing the second call to MoveNext.

juharr
  • 31,741
  • 4
  • 58
  • 93
  • Can you explain the use of the enumerator here? and how it replaces the need for the `Single()` function? – endorphin Mar 12 '11 at 08:43
  • @endorphin See the explanation I added to my answer. If that isn't clear let me know. – juharr Mar 12 '11 at 19:47
  • This is probably slightly less obvious to read, but it avoids using exceptions as control flow. And catching exceptions can be slow, which might be an issue if this is called frequently. And it's not doing anything the Single() method doesn't do itself anyway. So I'd go with this solution. – Niall Connaughton Apr 08 '13 at 23:58