Can a LINQ extension method create a new KeyValuePair with a new() .Value when a Where clause isn't satisfied
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
.
精彩评论