tags:

views:

3013

answers:

5

Hello,

This is my function:

    private IEnumerable<string> SeachItem(int[] ItemIds)
    {
        using (var reader = File.OpenText(Application.StartupPath + @"\temp\A_A.tmp"))
        {
            var myLine = from line in ReadLines(reader)
                         where line.Length > 1
                         let id = int.Parse(line.Split('\t')[1])
                         where ItemIds.Contains(id)
                         let m = Regex.Match(line, @"^\d+\t(\d+)\t.+?\t(item\\[^\t]+\.ddj)")
                         where m.Success == true
                         select new { Text = line, ItemId = id, Path = m.Groups[2].Value };
            return myLine;
        }
    }

I get a compile error,because "myLine" is not a IEnumerable[string] and I don't know how to write IEnumerable[Anonymous]

"Cannot implicitly convert type 'System.Collections.Generic.IEnumerable[AnonymousType#1]' to 'System.Collections.Generic.IEnumerable[string]'"

A: 

One thing to remember is that LINQ statements use deferred execution. What that means is that your LINQ statement doesn't actually execute until you either iterate over it in a foreach statement or you call the .ToList() method on myLine.
For your example try changing:

return myLine;

To:

return myLine.ToList();
Alexander Kahoun
The problem is in the procedure definition,so it should be "private List<string> SeachItem(int[] ItemIds)" ,but that doesn't work either."Cannot implicitly convert type List[AnoynymousType#1] to List[string].Please tell me if its just impossible to be done. :)
John
+4  A: 

You cannot declare IEnumerable<AnonymousType> because the type has no (known) name at build time. So if you want to use this type in a function declaration, make it a normal type. Or just modify your query to return a IENumerable<String> and stick with that type.

Or return IEnumerable<KeyValuePair<Int32, String>> using the following select statement.

select new KeyValuePair<Int32, String>(id, m.Groups[2].Value)
Daniel Brückner
When you say "select new {...}", you made an AnonymousType. If select a string, then it should work.
Richard Morgan
No but you can return IEnumerable and use the CastByExample trick. Not recommended though.
Alex James
+1  A: 

The method signature on SearchItem indicates that the method returns an IEnumerable<string> but the anonymous type declared in your LINQ query is not of type string. If you want to keep the same method signature, you have to change your query to only select strings. e.g.

return myLine.Select(a => a.Text);

If you insist on returning the data selected by your query, you can return an IEnumerable<object> if you replace your return statement with

return myLine.Cast<object>();

Then you can consume the objects using reflection.

But really, if your going to be consuming an anonymous type outside the method that it is declared in, you should define a class an have the method return an IEnumerable of that class. Anonymous types are convenience but they are subject to abuse.

Jason
Thank you both!
John
+1  A: 

Your function is trying to return IEnumerable<string>, when the LINQ statement you are executing is actually returning an IEnumerable<T> where T is a compile-time generated type. Anonymous types are not always anonymous, as they take on a specific, concrete type after the code is compiled.

Anonymous types, however, since they are ephemeral until compiled, can only be used within the scope they are created in. To support your needs in the example you provided, I would say the simplest solution is to create a simple entity that stores the results of your query:

public class SearchItemResult
{
    public string Text { get; set; }
    public int ItemId { get; set; }
    public string Path { get; set; }
}

public IEnumerable<SearchItemResult> SearchItem(int[] itemIds)
{
    // ...
    IEnumerable<SearchItemResult> results = from ... select new SearchItemResult { ... }
}

However, if your ultimate goal is not to retrieve some kind of object, and you are only interested in, say, the Path...then you can still generate an IEnumerable<string>:

IEnumerable<string> lines = from ... select m.Groups[2].Value;

I hope that helps clarify your understanding of LINQ, enumerables, and anonymous types. :)

jrista
FYI: It is not strictly correct to say that anonymous types are unique to the scope that they are created in. Anonymous types which are structurally equivalent in the same assembly but used in two different methods are actually shared across those methods. There are clever ways to take advantage of this fact, but I wouldn't recommend them; if you need to share a type across two methods, make it nominal.
Eric Lippert
Is that Eric Lippert of Microsoft fame? Now that you've opened the bag, you gotta let the cat out. How do you share an anonymous type across scopes? ;)
jrista
Very carefully. You have to use goofy method type inference tricks and captured outer variables. Maybe I'll do a blog on that some time.
Eric Lippert
+1  A: 

I am not necessarily recommending this... It is a kind of subversion of the type system but you could do this:

1) change your method signature to return IEnumerable (the non generic one)

2) add a cast by example helper:

public static class Extensions{
    public static IEnumerable<T> CastByExample<T>(
            this IEnumerable sequence, 
            T example) where T: class
    {
        foreach (Object o in sequence)
            yield return o as T;
    }
}

3) then call the method something like this:

var example = new { Text = "", ItemId = 0, Path = "" };
foreach (var x in SeachItem(ids).CastByExample(example))
{
    // now you can access the properties of x 
    Console.WriteLine("{0},{1},{2}", x.Text, x.ItemId, x.Path);
}

And you are done.

The key to this is the fact that if you create an anonymous type with the same order, types and property names in two places the types will be reused. Knowing this you can use generics to avoid reflection.

Hope this helps Alex

Alex James
Clever. But really, if someone is going to go to that much trouble, they should just encapsulate the data in a declared class.
Jason
As I said I'm not recommending this ;)
Alex James
... interestingly you only go to this trouble once, i.e. once you have the helper extension method this is easy to use again and again. Unlike rolling new types everywhere. Anyway this is a job for Tuple<T1,T2,T3> in .NET 4.0
Alex James