C
C#5mo ago
cow

✅ Nice way to get an item from a list only if there is one, otherwise return null?

Is there something like SingleOrDefault() that returns default instead of exception?
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list.Where(x => x == 'a').SingleOrDefault();
//Exception: Sequence contains more than one element
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list.Where(x => x == 'a').SingleOrDefault();
//Exception: Sequence contains more than one element
This throws an exception if more than 1, however I want 'default' if more than 1
47 Replies
Angius
Angius5mo ago
Just
var oneOrNone = list is [var el] ? el : null;
var oneOrNone = list is [var el] ? el : null;
MODiX
MODiX5mo ago
Angius
REPL Result: Failure
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list is [var el] ? el : null;
oneOrNone
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list is [var el] ? el : null;
oneOrNone
Exception: CompilationErrorException
- Type of conditional expression cannot be determined because there is no implicit conversion between 'char' and '<null>'
- Type of conditional expression cannot be determined because there is no implicit conversion between 'char' and '<null>'
Compile: 378.813ms | Execution: 0.000ms | React with ❌ to remove this embed.
Angius
Angius5mo ago
Well, with a cast
var oneOrNone = list is [var el] ? el : (char?)null;
var oneOrNone = list is [var el] ? el : (char?)null;
might work, or
var oneOrNone = (char?)(list is [var el] ? el : null;)
var oneOrNone = (char?)(list is [var el] ? el : null;)
maybe
cow
cow5mo ago
how does that ensure there is only 1?
Angius
Angius5mo ago
is [var el] Pattern matching
cow
cow5mo ago
hmm ok not familar with that syntax, i'll look it up
Angius
Angius5mo ago
Matches a list with only one element
mtreit
mtreit5mo ago
I would just check the count but I'm old fashioned.
cow
cow5mo ago
yeah just wondering if theres a "modern" way to do it or also was considering copying the SingleOrDefault source and modify to return Default instead of throw exception
MODiX
MODiX5mo ago
Angius
REPL Result: Success
var lists = new[]{
new[]{ 1, 2, 3, 4 },
new[]{ 1 },
new int[]{ }
};
foreach (var list in lists)
{
var match = list is [var el] ? el : (int?)null;
Console.WriteLine($"List: [{string.Join(',', list)}], Match: {(match is null ? "null" : match.ToString())}");
}
var lists = new[]{
new[]{ 1, 2, 3, 4 },
new[]{ 1 },
new int[]{ }
};
foreach (var list in lists)
{
var match = list is [var el] ? el : (int?)null;
Console.WriteLine($"List: [{string.Join(',', list)}], Match: {(match is null ? "null" : match.ToString())}");
}
Console Output
List: [1,2,3,4], Match: null
List: [1], Match: 1
List: [], Match: null
List: [1,2,3,4], Match: null
List: [1], Match: 1
List: [], Match: null
Compile: 493.418ms | Execution: 108.732ms | React with ❌ to remove this embed.
Angius
Angius5mo ago
You can, of course, do it the old fashioned way
var match = list.Count == 1 ? list[0] : (int?)null;
var match = list.Count == 1 ? list[0] : (int?)null;
cow
cow5mo ago
no that's not what im trying to acheive
Angius
Angius5mo ago
"Return if there's only one element, null otherwise" That's what it does Ah, it's about the default? If so, then instead of (int?)null or (char?)null use default(int) or default(char) or whatever else the type is
cow
cow5mo ago
no see the example i gave just like SingleOrDefault() behaviour, but return null/default instead of throw exception if there are more than 1
Angius
Angius5mo ago
So, in other words,
No description
Angius
Angius5mo ago
If there is one and only one element, return that element If there is zero or more than one elements, return default
cow
cow5mo ago
matching elements, not list.count
Angius
Angius5mo ago
? Which of my examples returns a count?
br4kejet
br4kejet5mo ago
Do you mean like list.Where(x => x == 'a').FirstOrDefault()? Can't remember if FirstOrDefault has a predicate overload or not
Angius
Angius5mo ago
It does, yeah
br4kejet
br4kejet5mo ago
Ah then i'd just put that lambda in FirstOrDefault and remove the Where
cow
cow5mo ago
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list.Where(x => x == 'a').NonThrowSingleOrDefault(); // should return default because multiple 'a' exist
var oneOrNone = list.Where(x => x == 'b').NonThrowSingleOrDefault(); // should return 'b'
var oneOrNone = list.Where(x => x == 'z').NonThrowSingleOrDefault(); // should return default because no 'z' exist
var list = new List<char> { 'a', 'b', 'c', 'a' };

var oneOrNone = list.Where(x => x == 'a').NonThrowSingleOrDefault(); // should return default because multiple 'a' exist
var oneOrNone = list.Where(x => x == 'b').NonThrowSingleOrDefault(); // should return 'b'
var oneOrNone = list.Where(x => x == 'z').NonThrowSingleOrDefault(); // should return default because no 'z' exist
FirstOrDefault will return 'a' , not default
Angius
Angius5mo ago
So you want default if there's a single element, and otherwise null?
cow
cow5mo ago
correct
Angius
Angius5mo ago
Good luck using it with reference types, then, since the default for them is null
br4kejet
br4kejet5mo ago
Make an extension method that counts how many items match the predicate, then if it's 1, return that element Otherwise return default
Angius
Angius5mo ago
But sure
var match = list.Count == 1 ? default(T) : (T?)null;
var match = list.Count == 1 ? default(T) : (T?)null;
T is whatever you want it to be
br4kejet
br4kejet5mo ago
list.Count(x => x == 'a') == 1 ? list.First(x => x == 'a') : default You could optimise it more but still
cow
cow5mo ago
yep ok thanks carrot I'll do that. I thought perhaps there was a more succint way with modern c# that i wasnt aware of
Angius
Angius5mo ago
Write an extension method
cow
cow5mo ago
public static TSource NonThrowSingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: return default(TSource);
case 1: return result;
}

return default(TSource);
//throw Error.MoreThanOneMatch();
}
public static TSource NonThrowSingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: return default(TSource);
case 1: return result;
}

return default(TSource);
//throw Error.MoreThanOneMatch();
}
oop https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,9cd0418fc9042fbe
Angius
Angius5mo ago
Something like that, yeah
cow
cow5mo ago
from there, just replace throw with return default
Angius
Angius5mo ago
I probably wouldn't be throwing on null arguments Since we have NRTs and all that But yeah I'd add where TSource : struct constraint maybe Unless you do expect this method to always return null with reference types
cow
cow5mo ago
oh just realised that was .net 4.8 source code, i'll see what the modern source is
cow
cow5mo ago
thank you can you F12 into it on Visual Studio? is there an option for that? to see c# source
cow
cow5mo ago
No description
cow
cow5mo ago
i can only get as far as that the function definition/comments not the actual code
Angius
Angius5mo ago
Not sure if VS comes with a decompiler by default Might need something like, dunno, DotPeek?
mtreit
mtreit5mo ago
VS can decompile
mtreit
mtreit5mo ago
When I F12 I get this:
No description
mtreit
mtreit5mo ago
Oh that's from SourceLink though Not actually decompiled. But it will decompile as well.
mtreit
mtreit5mo ago
You need these options enabled:
No description
cow
cow5mo ago
awesome 👍👍👍 thank you
Unknown User
Unknown User5mo ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX5mo ago
Use the /close command to mark a forum thread as answered