0

I have this property, which is working fine:

public IEnumerable<IGrouping<MessageType, Message>> MessageGroups
{
    get
    {
        return
            (from msg in _messages
                orderby msg.Type descending
                group msg by msg.Type);
    }
}

However, it's causing me to have to repeat the ugly-looking IEnumerable<IGrouping<MessageType, Message>> in several places in my code. I tried to make this easier on the eyes by defining a trivial wrapper interface like this:

public interface IMessageGroups :
    IEnumerable<IGrouping<MessageType, Message>> { }

and changing the property above to:

public IMessageGroups MessageGroups
{
    get
    {
        return
            (IMessageGroups)
            (from msg in _messages
                orderby msg.Type descending
                group msg by msg.Type);
    }
}

This builds fine, but at runtime I get:

Unable to cast object of type 'System.Linq.GroupedEnumerable`3[Message,MessageType,Message]' to type 'IMessageGroups'.

(project-specific namespaces removed from the error message)

What can I do to fix this?

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
JoelFan
  • 37,465
  • 35
  • 132
  • 205
  • You need to return an object from `MessageGroups` that implements the `IMessageGroups` interface. `IEnumerable>` does not implement the `IMessageGroups` interface. – dtb Oct 04 '10 at 21:48
  • Seems to me that you could avoid the problem entirely by not exposing this as a public property. Just expose an IEnumerable property and allow your client code to perform such a simple LINQ query. In my opinion there isn't much value in wrapping a straightforward grouping operation because the results will almost always be used as part of another query, in which case you could just as easily generate a new grouping query – cordialgerm Oct 05 '10 at 01:48

4 Answers4

3

You could try using a type alias:

using Foo = IEnumerable<IGrouping<MessageType, Message>>;

And then:

public Foo MessageGroups
{
    get
    {
        return
            (from msg in _messages
             orderby msg.Type descending
             group msg by msg.Type);
    }
}

Another possibility is to expand further your LINQ query and select some custom type you create after the grouping:

public IEnumerable<Foo> MessageGroups
{
    get
    {
        return
            (from msg in _messages
             orderby msg.Type descending
             group msg by msg.Type
             select new Foo { Messages = g, MessageType = g.Key }      
            );
    }
}

and Foo:

public class Foo
{
    public MessageType MessageType { get; set; }
    public IEnumerable<Message> Messages { get; set; }
}

If you don't care about lazy evaluation you could put a .ToArray() at the end of your LINQ query and the return type becomes simply Foo[].

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • The problem with that solution is that I have to repeat the "using" in every scope I use the alias. I tried just doing the alias in the place where I define MessageType and Message but it is not seen elsewhere – JoelFan Oct 04 '10 at 21:53
2

It builds fine because the compiler can never know for sure whether the returned type isn't actually a subclass that implements the interface. (Contrast this with a cast to a class instead and you get a compiler error since that is statically known.)

But the fact remains that you are trying to cast the result of the LINQ expression into an interface that it does not implement (the "wrapper" interface). That simply isn't going to work. And nothing you can really do to fix it short of declaring a class that implements it yourself, and actually doing the "wrapping" in your implementation (passing in the existing LINQ expression perhaps to the constructor).

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
1

The cast to IMessageGroups fails because the result of the query is an instance of IEnumerable<IGrouping<MessageType, Message>>, not an instance of IMessageGroups. But you could write a MessageGroups class and use it to wrap the result of the query :

public class MessageGroups : IMessageGroups
{
    private readonly IEnumerable<IGrouping<MessageType, Message>> _groups;

    public MessageGroups(IEnumerable<IGrouping<MessageType, Message>> groups)
    {
        _groups = groups;
    }

    public IEnumerable<IGrouping<MessageType, Message>> GetEnumerator()
    {
        return _groups.GetEnumerator();
    }

    public static MessageGroups Create(IEnumerable<IGrouping<MessageType, Message>> groups)
    {
        return new MessageGroups(groups);
    }
}

And use it like that:

public IMessageGroups MessageGroups
{
    get
    {
        return
            MessageGroups.Create(
                from msg in _messages
                orderby msg.Type descending
                group msg by msg.Type);
    }
}
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Thanks... You could improve it more by adding the alias suggested by others, and you can also make the constructor private – JoelFan Oct 04 '10 at 22:20
0

At the top of your cs file:

 using ASimpleName = Dictionary<string, Dictionary<string, List<string>>>;

Source

or in your case:

using ShortName = IEnumerable<IGrouping<MessageType, Message>>;
Community
  • 1
  • 1
WernerCD
  • 2,137
  • 6
  • 31
  • 51