Ivan Prodanov
Ivan Prodanov

Reputation: 35512

LINQ: How to declare IEnumerable[AnonymousType]?

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]'"

Upvotes: 29

Views: 58967

Answers (7)

Metafunken
Metafunken

Reputation: 1

The question was asked a long time ago, I hope it helps someone...

"You cannot declare IEnumerable", instead, must convert it to a "custom" IEnumerable:

public class MyString
    {
        public string String { get; set; } = string.Empty;
    }

public IEnumerable<MyString> GetMyStrings(List<string> Strings)
        {
            var AnonString = from S in Strings group S by S into Grouped select new { String = Grouped.Key };

            IEnumerable<MyString> Result = AnonString.Select(x => new MyString() { String = x.String }).ToArray();

            return Result;
        }

Regards.

Upvotes: 0

thehill
thehill

Reputation: 93

this link could be useful for others who end up here https://idreesdotnet.blogspot.com/2019/08/c-how-to-create-list-of-anonymous-type.html

the first solution (of 6) is delightlfully simple

1: First create the object(s) of anonymous type and then pass it to an array and call ToList() method.

   var o1 = new { Id = 1, Name = "Foo" };
   var o2 = new { Id = 2, Name = "Bar" };
   var list = new[] { o1, o2 }.ToList();

Upvotes: -2

John Smith
John Smith

Reputation: 7407

Return a ValueTuple instead of an anonymous class. Ex (using "named tuples")-

(Text: line, ItemId: id, Path: m.Groups[2].Value)

https://learn.microsoft.com/en-us/dotnet/csharp/tuples

Instead of-

new { Text = line, ItemId = id, Path = m.Groups[2].Value }

The ValueTuple is part of C# version 7 and was originally implemented as a separate NuGet package (System.ValueTuple). Starting with .NET 4.7 it is a built-in type. For .NET Core, versions prior to 2.0 required the NuGet package but it is built-in with version 2.0.

Upvotes: 0

Alex James
Alex James

Reputation: 20924

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

Upvotes: 14

jrista
jrista

Reputation: 32960

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. :)

Upvotes: 8

jason
jason

Reputation: 241631

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.

Upvotes: 9

Daniel Br&#252;ckner
Daniel Br&#252;ckner

Reputation: 59645

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)

Upvotes: 24

Related Questions