I used to think that List<T> is considered dangerous. My point is that, I think default(T) is not a safe return value! Many other people think so too Consider the following:
List<int> evens = new List<int> { 0, 2, 4, 6, , 8};
var evenGreaterThan10 = evens.Find(c=> c > 10);
// evenGreaterThan10 = 0 #WTF
default(T) for value types is 0, hence 0 is goona be returned is the above code se开发者_JAVA技巧gment!
I didn't like this, so I added an extension method called TryFind that returns a boolean and accepts an output parameter besides the Predicate, something similar to the famous TryParse approach. Edit: Here's my TryFind extension method:public static bool TryFind<T>(this List<T> list, Predicate<T> predicate, out T output)
{
int index = list.FindIndex(predicate);
if (index != -1)
{
output = list[index];
return true;
}
output = default(T);
return false;
}
What's a your way to do Find on generic Lists?
I don't. I do .Where()
evens.Where(n => n > 10); // returns empty collection
evens.Where(n => n > 10).First(); // throws exception
evens.Where(n => n > 10).FirstOrDefault(); // returns 0
The first case just returns a collection, so I can simply check if the count is greater than 0 to know if there are any matches.
When using the second, I wrap in a try/catch block that handles InvalidOperationException specfically to handle the case of an empty collection, and just rethrow (bubble) all other exceptions. This is the one I use the least, simply because I don't like writing try/catch statements if I can avoid it.
In the third case I'm OK with the code returning the default value (0) when no match exists - after all, I did explicitly say "get me the first or default" value. Thus, I can read the code and understand why it happens if I ever have a problem with it.
Update:
For .NET 2.0 users, I would not recommend the hack that has been suggested in comments. It violates the license agreement for .NET, and it will in no way be supported by anyone.
Instead, I see two ways to go:
Upgrade. Most of the stuff that runs on 2.0 will run (more or less) unchanged on 3.5. And there is so much in 3.5 (not just LINQ) that is really worth the effort of upgrading to have it available. Since there is a new CLR runtime version for 4.0, there are more breaking changes between 2.0 and 4.0 than between 2.0 and 3.5, but if possible I'd recommend upgrading all the way to 4.0. There's really no good reason to be sitting around writing new code in a version of a framework that has had 3 major releases (yes, I count both 3.0, 3.5 and 4.0 as major...) since the one you're using.
Find a work-around to the
Find
problem. I'd recommend either just usingFindIndex
as you do, since the -1 that is returned when nothing is found is never ambiguous, or implementing something withFindIndex
as you did. I don't like theout
syntax, but before I write an implementation that doesn't use it, I need some input on what you want returned when nothing was found.
Until then, theTryFind
can be considered OK, since it aligns with previous functionality in .NET, for exampleInteger.TryParse
. And you do get a decent way to handle nothing found doingList<Something> stuff = GetListOfStuff(); Something thing; if (stuff.TryFind(t => t.IsCool, thing)) { // do stuff that's good. thing is the stuff you're looking for. } else { // let the user know that the world sucks. }
Instead of Find
you could use FindIndex
. It returns the index of the element found or -1
if no element was found.
http://msdn.microsoft.com/en-us/library/x1xzf2ca%28v=VS.80%29.aspx
It is ok if you know there are no default(T)
values in your list or if the default(T)
return value can't be the result.
You could implement your own easily.
public static T Find<T>(this List<T> list, Predicate<T> match, out bool found)
{
found = false;
for (int i = 0; i < list.Count; i++)
{
if (match(list[i]))
{
found = true;
return list[i];
}
}
return default(T);
}
and in code:
bool found;
a.Find(x => x > 5, out found);
Other options:
evens.First(predicate);//throws exception
evens.FindAll(predicate);//returns a list of results => use .Count.
It depends on what version of framework you can use.
The same answer I gave on Reddit.
Enumerable.FirstOrDefault( predicate )
If there is a problem with the default value of T, use Nullable to get a more meaningful default value :
List<int?> evens = new List<int?> { 0, 2, 4, 6, 8 };
var greaterThan10 = evens.Find(c => c > 10);
if (greaterThan10 != null)
{
// ...
}
This also requires no additional call of Exists() first.
Doing a call to Exists()
first will help with the problem:
int? zz = null;
if (evens.Exists(c => c > 10))
zz = evens.Find(c => c > 10);
if (zz.HasValue)
{
// .... etc ....
}
slightly more long-winded, but does the job.
Just for fun
evens.Where(c => c > 10)
.Select(c => (int?)c)
.DefaultIfEmpty(null)
.First();
Inspired by Jaroslav Jandek above I would make an extension that returns bool true if match and passes the object in out if match:
static class Extension
{
public static bool TryFind<T>(this List<T> list, Predicate<T> match, out T founditem)
{
for (int i = 0; i < list.Count; i++)
{
if (match(list[i]))
{
founditem = list[i];
return true;
}
}
founditem = default(T);
return false;
}
}
Then it can used on a List object 'a' and called like this using 'out var' syntax:
if (a.TryFind(x => x > 5, out var founditem)){
//enter code here to use 'founditem'
};
精彩评论