Why doesn't the Controls collection provide all of the IEnumerable methods? - c#

I'm not for sure how the ControlCollection of ASP.Net works, so maybe someone can shed some light on this for me.
I recently discovered the magic that is extension methods and Linq. Well, I was very sad to find that this isn't valid syntax
var c=Controls.Where(x => x.ID=="Some ID").SingleOrDefault();
However from what I can tell, Controls does implement the IEnumerable interface which provides such methods, so what gives? Why doesn't that just work? I have found a decent work around for this issue at least:
var list = (IEnumerable<Control>)Controls;
var this_item = list.Where(x => x.ID == "Some ID").SingleOrDefault();

No, IEnumerable doesn't have many extension methods on it: IEnumerable<T> does. They are two separate interfaces, although IEnumerable<T> extends IEnumerable.
The normal LINQ ways of converting are to use the Cast<T>() and OfType<T>() extension methods which do extend the nongeneric interface:
IEnumerable<TextBox> textBoxes = Controls.OfType<TextBox>();
IEnumerable<Control> controls = Controls.Cast<Control>();
The difference between the two is that OfType will just skip any items which aren't of the required type; Cast will throw an exception instead.
Once you've got references to the generic IEnumerable<T> type, all the rest of the LINQ methods are available.

This is just because the ControlCollection class came around before generics; so it implements IEnumerable but not IEnumerable<Control>.
Fortunately, there does exist a LINQ extension method on the IEnumerable interface that allows you to generate an IEnumerable<T> through casting: Cast<T>. Which means you can always just do this:
var c = Controls.Cast<Control>().Where(x => x.ID == "Some ID").SingleOrDefault();

In addition to the answers provided by Jon Skeet and Dan Tao, you can use query expression syntax by explicitly providing the type.
Control myControl = (from Control control in this.Controls
where control.ID == "Some ID"
select control).SingleOrDefault();

Linq utilized Generic Collections. ControlsCollection implements IEnumerable not IEnumberable<T>
If you notice this will not work
((IEnumerable)page.Controls).Where(...
However, this does
((IEnumerable<Control>)page.Controls).Where(...
You can either cast to Generic IEnumerable<T> or access an extension method that does, like so:
page.Controls.OfType<Control>().Where(c => c.ID == "Some ID").FirstOrDefault();

Related

Does usage of contains on IEnumerable cast it to a List?

I'm using Linq to filter Data I get from the database. Due to design choices made 1 method returns me an IEnumerable<int> which I then use for a linq statement to see which IDs are permitted to be returned (code follows below). My question here is as I'm not seeing anything there in the documentation: Does the Contains method implicitly cast the IEnumerable to a List for the statement to be executed? (If so the question is if using List in the first place instead of IEnumerable is better).
Code Example
private List<MyData> GetAllPermittedData()
{
IEnumerable<int> permitteddIds = GetPermittedIDs();
return (from a in MyDataHandler.GetAllData() where permittedIds.Contains(a.Id)
select a);
}
Like I asked above I'm not sure if the Contains part implicitly converts permittedIds into a List<int> (for/inside the use of the Contains statement). If this is the case then a followup question would be if it is not better to already use the following statement instead (performance-wise):
private List<MyData> GetAllPermittedData()
{
List<int> permitteddIds = GetPermittedIDs().ToList();
return (from a in MyDataHandler.GetAllData() where permittedIds.Contains(a.Id)
select a);
}
The LINQ operator will attempt to cast it to ICollection<T> first. If the cast succeeds, it uses that method. Since List<T> implements this interface, it will use the list's contain method.
Note that if you use the overload that accepts an IEqualityComparer, it must iterate over the enumerable and the ICollection shortcut is not taken.
You can see this implementation in the .NET Framework reference source:
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value) {
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null) return collection.Contains(value);
return Contains<TSource>(source, value, null);
}
Jon Skeet also has a good (and lengthy) blog series called "Reimplementing LINQ" where he discusses the implementation in depth. He specifically covers Contains in part 32 of his blog.
The Contains method may try to cast the passed IEnumerable<T> to IList<T> or to ICollection<T>. If the cast succeeds, it may directly use the methods of IList<T>, otherwise it will enumerate over the full sequence.
Note that I am writing may because this is implementation-specific and it is not specified in the docs. As such, it could be different across .NET versions and also in alternative implementations such as Mono.
Your advantage by providing only an IEnumerable<T> is that you have more freedom to exchange the object returned from that property without changing the public interface. The performance cost of the attempted cast to IList<T> or similar should be negligible.
In any case, this way is more performant than your suggestion of calling ToList, as that will actually create a new List<T> and copy all items from the enumeration into it.
Contains exists as an extension method for IEnumerable<T>. But you con't need to convert your IEnumerable to a List<T> with ToList(), you could simply use that IEnumerable<T> to fill a HashSet<T>:
var permitteddIds = new HashSet<int>(GetPermittedIDs());

Generic method for both IEnumerable<T> and IQueryable<T>

I was trying to make a generic method for paging both IEnuemrable<T> and IQueryable<T>
Like this:
public T Paginate<T, TS>(T list) where T : IEnumerable<TS>
{
CheckValidityAndClamp();
return (T)(list.Skip(Page*PageSize).Take(PageSize));
}
When I passed in a List<int> instance it compiles fine.
But when run it gives a cast exception:
System.InvalidCastException : An object of type '<TakeIterator>d__3a`1[System.Int32]' can not be converted to the type 'System.Collections.Generic.List`1[System.Int32]'.
Why is that? A List<int> implements IEnumerable<int> so why the cast exception?
You're not returning a list. Take is implemented with an iterator block, which means that the actual type is a type with no compile time identifier (which is why it looks so weird) and it implements IEnumerable. It is not a List, which you're trying to cast it to.
On top of that, your method can't actually work for any IQueryable objects. It's calling the implementation of Skip that accepts an IEnumerable (because that's what the generic constraint tells the compiler it must implement) so if it were an IQueryable (even if you resolved the messy cast issue) you wouldn't be using the query provider to translate the Skip call. You need to have two overloads here, one for IQueryable and one for IEnumerable; this is pretty much inherent to the problem given that there are two Skip and Take methods that you need to call, one for each of IEnumerable and IQueryable.
To expand on Servy's (correct) answer - another issue is that Skip and Take are extension methods, which is syntactic sugar for calling static methods. Since static methods are bound at compile time, the best information that the compiler has is that T is an IEnumerable<TS>, so the compiler binds Skip and Take to the static methods of Enumerable. There's no way to dynamically bind static methods at run-time. That is why there are two different classes for the extension methods on IEnumerable and IQueryable (Enumerable and Queryable)
You need two overloads.
You get this as you cannot cast the IEnumerable to List (This is why LINQ has .ToList() extension). I think what you want is something like this:
public IEnumerable<T> Paginate<T>(IEnumerable<T> list)
{
return list.Skip(Page * PageSize).Take(PageSize);
}
Which will work with any source that implements IEnumerable (List, T[], IQueryable)
You can then call it like this:
IEnumerable<int> list = someQueryResult;
IEnumerable<int> page = class.Paginate<int>(list);
As pointed out in other answers. IQueryable has it's own set of extension methods. So all though this will work, the paging will not be done as the data-source, as it will use the IEnumerable extensions

Concise syntax to invoke instance methods after invoking LINQ methods

I have a type that wraps an IEnumerable<T> and contains some additional logic, implementing an interface which we'll call IMyIterable<T>. On one hand, I want people to use LINQ methods to filter and process IMyIterable<T> (by actually applying them to the underlying object), but on the other hand, I still want to fluently expose IMyIterable<T>'s custom instance functionality (e.g state information) after the LINQ methods have been applied. e.g.
var query = myIterable.Select(x => x.whatever);
//query is now of a completely different type than IMyIterable<T>
var result = query.MyCustomThingy();
//But I really want MyCustomThingy to work too.
I know a solution would be:
var query = myIterable.Linq(x => x.Select(y => y.Whatever));
But the extra brackets are unseemly to me. Are there any other ways I could do this? I'm not actually asking for the method Select() to return a custom class (though if there are neat ways to do this, e.g. involving code generation, I'm definitely aboard), just some neater syntax.
LINQ is not tied to IEnumerable. You can implement the LINQ methods you need on IMyIterable itself (either as instance methods or as extension methods). This way you can even use the linq syntax directly on a variable of type IMyIterable.
E.g.:
public interface IMyIterable<T>
{
IMyIterable<TOut> Select<TOut>(Func<T, TOut> selector);
IMyIterable<T> Where(Func<T, bool> predicate);
// ...
}
This would allow you to write something like this:
IMyIterable<T> source = ...;
var query = from item in source
where item.X == Y
select new {...}
Could you implement the methods on IMyIterable as extension methods to IEnumerable instead ? Not knowing exactly what this interface does makes it hard to come up with a solution.
Instead of deriving from IEnumerable, you could create a bunch of equivalent methods on IMyIterable (returning IMyIterable) and forward them to the inner IEnumerable instance.
However this seems like a lot of work to go around a cast. I guess your suggested .Linq() solution would work OK.

How to .skip() . take() elements on IList?

Tried with :
IList<T> list= (from ...linq...).ToList();
list = list.Skip(0).Take(10);
but seems that there aren't those functions. Any other further way to do this?
First make sure you are referencing System.Linq.
Secondly the extension methods work with a stronly typed list. so use list.OfType<object>().Skip(0).Take(10)
If you know the items in your list are of a particular type then replace object with that type.
Thirdly, Skip(0) is redundant.
Additionally your code example is trying to assign from a IEnumerable to an IList you need the ToList() on your result.
list = list.Skip(0).Take(10).ToList();
have you got System.Linq referenced?
using System.Linq;
Those are method extensions that are defined in namespace System.Linq. Be sure to reference it:
using System.Linq;
Skip and Take are extensions to IEnumerable<T>. As you're dealing with the (non-generic) IList these wont be available. However if you know what type is in your IList then this should work fine:
list.OfType<YourType>().Skip(1).Take(10)
The reason this works is that OfType<T> is an extension method on the non-generic IEnumerable (which IList inherits from) and return a generic IEnumerable<T>.
references:
OfType<T>
If you mean on a non-generic IList, you need to use Cast or OfType to obtain a generic sequence first. If you expect all the elements to be of the desired type, use Cast. If you want to ignore elements of the "wrong" type, use OfType. (I prefer Cast where possible, as I generally want to get an exception if my data isn't as expected.)
For example:
IList list = ...;
var elements = list.Cast<string>()
.Skip(10)
.Take(20);
You also need a reference to the System.Core assembly and a using directive for System.Linq.
EDIT: Now that we know you've got an IList<T>, the problem is clearer. It's not that Skip and Take aren't found, it's that they don't return an IList<T>. If you change your code to:
IEnumerable<T> list= (from ...linq...).ToList();
list = list.Skip(0).Take(10);
it should work fine.

Why can't I use a Linq query on a Visio Masters collection?

I am trying to do the following Linq query on a Visio Masters collection:
List<string> allMasterNames = (from master in myStencil.Masters select master.NameU).ToList<string>
I get the following error:
Could not find an implementation of
the query pattern for source type
'Microsoft.Office.Interop.Visio.Masters'.
'Select' not found. Consider
explicitly specifying the type of the
range variable 'master'.
From reading around this error seems to occur when the queried object does not implement IEnumerable<T> or IQueryable<T>. Is that the case here, or is it something else?
Yes, it is because it's not IEnumerable<T>, IQueryable<T> and it doesn't have its own custom Select method written.
Contary to popular believe you don't have to implement those interfaces to have LINQ support, you just need to have the methods they compile down to.
This is fine:
public class MyClass {
public MyClass Select<MyClass>(Func<MyClass, MyClass> func) { ... }
}
var tmp = from m in new MyClass()
select m;
Sure a .ToList() wont work though ;)
As you solving your problem try using the Cast<T>() extension method, that'll make it an IEnumerable<T> and allow you to LINQ to your hearts content.
As the exception message suggests, consider explicitly specifying the type of the range variable 'master'.
from Visio.Master master in myStencil.Masters select master.NameU
According to Reflector ...
public class MastersClass : IVMasters, Masters, EMasters_Event, IEnumerable
{
// more
}
None of the others are IEnumerable<T> either. But, good ol IEnumerable is there, so you can for each.
I cannot try this piece of code.
(from master in myStencil.Masters.Cast<Masters> select master.NameU).ToList<string>)
The gist is to use Enumerable.Cast method, to convert a non-generic list to a generic one.
Does that work?

Categories

Resources