views:

171

answers:

4

I have a bas class called Media with two classes that inherit from it, Photo and Video. I am trying to create a collection for the media base class to hold those photo and video objects. So I have created a MediaList class as follows:

public class MediaList: ICollection<Media>
{
    private readonly XElement _mediaElement;

    public MediaList(XElement mediaElement)
    {
        _mediaElement = mediaElement;            
    }

    public IEnumerator<Media> GetEnumerator()
    {
        foreach (XElement element in _mediaElement.Elements())
        {
            Media media;
            switch (element.Name.LocalName)
            {
                case "video":
                    media = new Video(element);
                    break;
                case "photo":
                    media = new Photo(element);
                    break;
                default:
                    media = null;
                    break;
            }
            yield return media;
        }
    }

    //Rest of ICollection Implementation
}

When I iterate the list I get the following exception:

The value "Tool.Photo" is not of type "Tool.Video" and cannot be used in this generic collection.

If I am returning a Media object, why is it throwing the exception? Is there a better way to get around this?

+1  A: 

Shouldn't the method with the yield return be typed to return IEnumerable<Media>, not IEnumerator<Media> ?

But more basically, this pattern leaves me with a bad smell. Every time you use foreach on this "collection", you will be creating (instantiating) new instances of each Photo or Video object in the _mediaElement.Elements() list. Is this really what you want?

Charles Bretana
no what he has is fine.
Yuriy Faktorovich
Isn't the yield return returning Media objects? (not an Enumerator of Media objects.)
Charles Bretana
An Enumerator is state-maintenance object created when calling foreach to keep track of which object in the collection is the current one, and increment that state to the next one each time foreach iterates. When using yield return, an enumerator is created for you by the c# compiler.
Charles Bretana
Under normal circumstances I would agree with you, but in this case the media objects are just fly weights and the collections is only iterated through once.
Geoff
Perhaps I am not clear, but doesn't the GetEnumerator() methods' return type (which you have as IEnumerator<Media>) STILL need to be the return type required by yield return (which is IEnumerable<>)? I mean if the method is to return IEnumerator<>, where in GetEnumerator() is an object which implements IEnumerator being constructed ?? Your yield return returns either a Video or a Photo object. Does the Video class and the Photo class implement IEnumerator<> ?? I think not...
Charles Bretana
A: 

Looks like you're trying to add Media instances into a Video collection, and it blows up because one or more of them is a Photo -- you're trying to add a photo to a video collection. The error is in your client code, not in the generated enumerator.

Rytmis
+2  A: 

Your mistake is elsewhere. What you have written is fine, as long as both Video and Photo inherit from Media. Maybe you are trying to cast it incorrectly somewhere else.

Yuriy Faktorovich
After digging around, you are right, my problem is elsewhere. The questions is where? Sigh back to stepping through code :-)
Geoff
+3  A: 

Try this...

class Program
{
    static void Main(string[] args)
    {
        var array = new string[] { "video", "photo", "hurf", "photo" };
        var ml = new MediaList(array);
        foreach(var element in ml)
            Console.WriteLine(element.GetType().Name);
        Console.Read();
    }
}

public class Media { }
public class Video : Media { }
public class Photo : Media { }

public class MediaList
{
    private string[] elements;
    public MediaList(string[] elements) { this.elements = elements; }
    public IEnumerator<Media> GetEnumerator()
    {
        foreach (string s in elements)
            switch (s)
            {
                case "video":
                    yield return new Video();
                    break;
                case "photo":
                    yield return new Photo();
                    break;
            }
    }
}

You can slap this into a console app to test it.

Notice a couple different things. First, you never yield return a null. That doesn't have anything to do with your issue, but nobody expects an enumerable to return a null and will cause you problems later on. Second, I'm not casting what I return. All casting is implicit or handled by the compiler, so there isn't any need for me to do this. Third, this compiles and works, as does your original code. Your issue is happening somewhere else, as you will find if you drop this code into a console app and test it.

Will
+1 That is the exact code that I had in the beginning until I went down the wrong rabbit whole :-)
Geoff