views:

1146

answers:

3

Some background info;

  • LanguageResource is the base class
  • LanguageTranslatorResource and LanguageEditorResource inherit from LanguageResource
  • LanguageEditorResource defines an IsDirty property
  • LanguageResourceCollection is a collection of LanguageResource
  • LanguageResourceCollection internally holds LanguageResources in Dictionary<string, LanguageResource> _dict
  • LanguageResourceCollection.GetEnumerator() returns _dict.Values.GetEnumerator()

I have a LanguageResourceCollection _resources that contains only LanguageEditorResource objects and want to use LINQ to enumerate those that are dirty so I have tried the following. My specific questions are in bold.

  1. _resources.Where(r => (r as LanguageEditorResource).IsDirty)

    neither Where not other LINQ methods are displayed by Intellisense but I code it anyway and am told "LanguageResourceCollection does not contain a definition for 'Where' and no extension method...".

    Why does the way that LanguageResourceCollection implements IEnumerable preclude it from supporting LINQ?

  2. If I change the query to

    (_resources as IEnumerable<LanguageEditorResource>).Where(r => r.IsDirty)

    Intellisense displays the LINQ methods and the solution compiles. But at runtime I get an ArgumentNullException "Value cannot be null. Parameter name: source".

    Is this a problem in my LINQ code?
    Is it a problem with the general design of the classes?
    How can I dig into what LINQ generates to try and see what the problem is?

My aim with this question is not to get a solution for the specific problem, as I will have to solve it now using other (non LINQ) means, but rather to try and improve my understanding of LINQ and learn how I can improve the design of my classes to work better with LINQ.

+6  A: 

It sounds like your collection implements IEnumerable, not IEnumerable<T>, hence you need:

_resources.Cast<LanguageEditorResource>().Where(r => r.IsDirty)

Note that Enumerable.Where is defined on IEnumerable<T>, not IEnumerable - if you have the non-generic type, you need to use Cast<T> (or OfType<T>) to get the right type. The difference being that Cast<T> will throw an exception if it finds something that isn't a T, where-as OfType<T> simply ignores anything that isn't a T. Since you've stated that your collection only contains LanguageEditorResource, it is reasonable to check that assumption using Cast<T>, rather than silently drop data.

Check also that you have "using System.Linq" (and are referencing System.Core (.NET 3.5; else LINQBridge with .NET 2.0) to get the Where extension method(s).

Actually, it would be worth having your collection implement IEnumerable<LanguageResource> - which you could do quite simply using either the Cast<T> method, or an iterator block (yield return).

[edit] To build on Richard Poole's note - you could write your own generic container here, presumably with T : LanguageResource (and using that T in the Dictionary<string,T>, and implementing IEnumerable<T> or ICollection<T>). Just a thought.

Marc Gravell
+1  A: 

In addition to Marc G's answer, and if you're able to do so, you might want to consider dropping your custom LanguageResourceCollection class in favour of a generic List<LanguageResource>. This will solve your current problem and get rid of that nasty .NET 1.1ish custom collection.

Richard Poole
Well, it would still need to get from LanguageResource to LanguageEditorResource, and note the internal use of Dictionary<,> - it isn't *just* a basic collection wrapper.
Marc Gravell
+1  A: 

How can I dig into what LINQ generates to try and see what the problem is?

Linq isn't generating anything here. You can step through with the debugger.

to try and improve my understanding of LINQ and learn how I can improve the design of my classes to work better with LINQ.

System.Linq.Enumerable methods rely heavily on the IEnumerable< T > contract. You need to understand how your class can produce targets that support this contract. The type that T represents is important!

You could add this method to LanguageResourceCollection:

public IEnumerable<T> ParticularResources<T>()
{
  return _dict.Values.OfType<T>();
}

and call it by:

_resources
  .ParticularResources<LanguageEditorResource>()
  .Where(r => r.IsDirty)

This example would make more sense if the collection class didn't implement IEnumerable< T > against that same _dict.Values . The point is to understand IEnumerable < T > and generic typing.

David B