开发者

Can a LINQ extension method create a new KeyValuePair with a new() .Value when a Where clause isn't satisfied

开发者 https://www.devze.com 2023-02-16 12:35 出处:网络
I have a collection List<KeyValuePair<string, Details>> x where public class Details { private int x;

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开发者_如何学JAVA 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!


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消