Quick LINQ question
-
Ok, so I have a couple classes that implement the same interface:
IBlarp DogBlarp : IBlarp CatBlarp : IBlarp
I have a function that gives me a list of all blarps that belong to a user:
List<IBlarp> allBlarps = user.GetAllBlarps();
To process this list, I need sublists based on type. (Basically, I need to split the list of IBlarps into two lists, one of DogBlarps and one of CatBlarps.)
I tried this in LINQ:
var catBlarps = (List<CatBlarp>)allBlarps.Where( b => b is CatBlarp );
Looks like it should work, right? Should be completely unambiguous what I'm doing, right?
Unfortunately, no. It produces an illegal cast exception because it's trying to cast List<IBlarp> to a List<catBlarp>. Hm.
So the question is: how do I make this work? Hopefully in a one-liner.
-
Interesting, I would expect this to work.
I do have vague recollections about weirdness surrounding casting ILists, but I'm too out of C# to remember.
Now I'm curios, starting LinqPad...
-
Interesting, I would expect this to work.
Me too! The code is 100% unambiguous!
It's when the magic of C# + LINQ breaks down that you realize the magic isn't magic, but actually hundreds of guys somewhere figuring out edge-cases and they missed one.
Anyway, it looks like the LINQ "OfType<Type>" function is what I need, I'm trying that now, but it takes a few minutes to test as it's a runtime error.
-
Wait a sec. Shouldn't it be
var catBlarps = (List<CatBlarp>)allBlarps.Where( b => b is CatBlarp ).ToList();
...? Testing....
-
The same difference....
Cannot convert type 'System.Collections.Generic.List<UserQuery.IBlarg>' to 'System.Collections.Generic.List<UserQuery.Blarg>'
Hmm...
-
var catBlarps = (List<CatBlarp>)allBlarps.Where( b => b is CatBlarp );
This seems to work:
var catBlarps = allBlarps.Where( b => b is CatBlarp ).OfType<CatBlarp>();
Note you can't use just OfType without also checking type within the LINQ statement (even though that also seems like it should work), or you'll get invalid casts.
-
I came up with this
void Main() { var iblargs = new List<IBlarg>(); var blargs = iblargs.Where(x => x is Blarg).Cast<Blarg>().ToList(); Console.WriteLine(blargs); } public interface IBlarg {} public class Blarg : IBlarg {}
-
That works too, but you have to run .ToList() earlier than absolutely necessary which enumerates the entire list at that point.
-
var blargs = iblargs.Where(x => x is Blarg).Select(x => (Blarg)x)
?
-
void Main() { var iblargs = new List<IBlarg>(); var blargs = iblargs.OfType<Blarg>().ToList(); Console.WriteLine(blargs); } public interface IBlarg {} public class Blarg : IBlarg {}
Slightly simplier.
You maybe:
Dictionary<Type, List<IBlarg>> dict = iblargs.GroupBy(x => x.GetType()).ToDictionary(x => x.Key, x => x.ToList())
totally untested
-
That works too, but you have to run .ToList() earlier than absolutely necessary which enumerates the entire list at that point.
I thought you wanted a list, but now I see you end up just getting an enumerable.
Note you can't use just OfType without also checking type within the LINQ statement (even though that also seems like it should work), or you'll get invalid casts.
Actually, it seems you can do without the
Where
part.void Main() { var iblargs = new List<IBlarg> { new Blarg1(), new Blarg2() }; var blargs = iblargs.OfType<Blarg1>().ToList(); Console.WriteLine(blargs.GetType()); Console.WriteLine(blargs.Count); } public interface IBlarg {} public class Blarg1 : IBlarg {} public class Blarg2 : IBlarg {}
Output:
System.Collections.Generic.List`1[UserQuery+Blarg1]
1
-
Note you can't use just OfType without also checking type within the LINQ statement (even though that also seems like it should work), or you'll get invalid casts.
Odd. Works for me...
-
So the question is: how do I make this work? Hopefully in a one-liner.
var catBlarps = from b in allBlarps where b is CatBlarp select (CatBlarp)b;
-
As far as I know, that's the whole point of OfType.
-
var catBlarps = allBlarps.Where( b => b is CatBlarp ).OfType<CatBlarp>();
Note you can't use just OfType without also checking type within the LINQ statement (even though that also seems like it should work), or you'll get invalid casts.
Taken from a decompile of System.Core.dll:
private static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source) { foreach (object current in source) { if (current is TResult) { yield return (TResult)((object)current); } } yield break; }
-
Ok well, the source I read on OfType said it didn't, and I have actual work to complete instead of wacking off in dotnetfiddle so congratulations U R MR SMARTY MAN
-
In that case, it reduces to:
var catBlarps = allBlarps.OfType<CatBlarp>();
It works in my testing.
-
Gotcha.
It still bugs me that the first version I tried didn't work, though. But now that I know about OfType<blah> I guess that's a better way of solving the problem.
-
Well, the reason it didn't work is that
is
doesn't actually do the cast. So the items remainIBlarp
, andList<T>
doesn't know that anIBlarp
can be cast into aCatBlarp
safely, so it doesn't think something generic on them can be cast that way either. If you'd doneallBlarps.Select(blarp => blarp as CatBlarp).Where(blarp => blarp != null);
, it'd be cast correctly already.This makes me want to read up on
A<out T>
and such again, since I remember none of that.
-
Hindler-Milney doesn't (automatically) have dependent types, which you would need in order to have a stream with multiple element types. (In fact, if you have the full power of dependent types, your language is total.)
If you really want to do something like that, you'll need to reify the interface as a type, and dispatch based on the data constructors.
Otherwise, your only option is to build a stream/list for each type.
-
Well, the reason it didn't work is that is doesn't actually do the cast.
Well duh.
But that doesn't change the fact that the code as written is completely unambiguous and Linq/Intellisense should have figured it the hell out.
-
completely unambiguous
It's not. It's not even well-typed. http://en.wikipedia.org/wiki/Parametric_polymorphism
-
It's unambiguous to a human, but the compiler doesn't have that level of intelligence
-
Oh I see, is this thread in "pile-on Blakeyrat" mode now?
I'll just mute it.
-
I was hoping you would learn something, since you don't seem to understand Linq's type system.
I.e., I was trying to be genuinely helpful. But it is you, of course. Silly me!
-
It's not ambiguous, no, but you're also not using some kind of imaginary
IEnumerable<in T>
, so it's unambiguously wrong.
-
The root problem seems to be that blakeyrat doesn't distinguish between
List<CatBlarp>
andList<IBlarp>
which holds onlyCatBlarp
s. Too bad he already ragequitted, because I think I'd enjoy this rant about how they're the same.
-
I think VB.NET wouldn't error out on that.
-
Does it do type erasure?
-
I think VB.NET wouldn't error out on that.
I think VB is a little more liberal about what it accepts; C#, on the other hand, is pretty strict
-
Odd. Works for me...
Ok well, the source I read on OfType said it didn't, and I have actual work to complete instead of wacking off in dotnetfiddle so congratulations U R MR SMARTY MAN
Do not help this man.
-
Do NOT help this man.
Well I got a compliment, I guess? For Blakey that's something huge.
-
I'm not sure how much
Option Strict
would affect that, though.
-
.NET is .NET and a list is a list:
Compilation error (line 19, col 0): Value of type 'System.Collections.Generic.List(Of ITest)' cannot be converted to 'System.Collections.Generic.List(Of Test1)'.
-
I guess I misremembered something, then.
VB.NET lets you treat elements of the List as Test1s (without explicit casts), though - unlike C#.
-
Ooooh yeah I almost missed that (at first I too thought it should work, but now i see it). Casting
List<IBlarp>
toList<CatBlarp>
would not be valid even outside of LINQ (there's no way the compiler could possibly know or guarantee that yourList<IBlarp>
is only going to contain CatBlarps), which is essentially what is happening (allBlarps.Where returns, IIRC,IEnumerable<IBlarp>
, which you then try to cast toList<CatBlarp>
, which as expected fails).EDIT: Damn you Discourse, for interpreting my post as wonky HTML.
-
there's no way the compiler could possibly know or guarantee that your List<IBlarp> is only going to contain CatBlarps
Well it could, technically, attempt to cast every element and give a runtime error on a cast failure. That opens a whole different can of worms, mostly in terms of language spec, but I guess it's not impossible...
-
True, but I'd prefer it to fail at compile-time. I mean, it's not like it's hard to make right
-
Well it could, technically, attempt to cast every element and give a runtime error on a cast failure. That opens a whole different can of worms, mostly in terms of language spec, but I guess it's not impossible...
It could, but then that wouldn't be the compiler, that'd be the runtime. And one of the main things I think people really like about C# is how fantastic the compile time safety is.
-
The can of worms was opened years ago. If you make a new collection, say,
class InsaneCollection<in T> where T : IBlarp
, it will actually do that.
-
Do not help this man.
Yeah, he acts like he's entitled to free consulting work!