2010-01-29 77 views
2

我看到的SelectMany的所有例子都是扁平數組的數組等等。我對這個問題有不同的看法。如何通過Linq將一個類型(而不是一個枚舉)展平?

我有一個類型的數組,我想將該類型的內容提取到一個流中。這裏是我的示例代碼:

public class MyClass 
{ 
    class Foo 
    { 
     public int X, Y; 
    } 

    static IEnumerable<int> Flatten(Foo foo) 
    { 
     yield return foo.X; 
     yield return foo.Y; 
    } 

    public static void RunSnippet() 
    { 
     var foos = new List<Foo>() 
      { 
       new Foo() { X = 1, Y = 2 }, 
       new Foo() { X = 2, Y = 4 }, 
       new Foo() { X = 3, Y = 6 }, 
      }; 

     var query = foos.SelectMany(x => Flatten(x)); 
     foreach (var x in query) 
     { 
      Console.WriteLine(x); 
     } 
    } 
} 

這個輸出,我想什麼:1,2,2,4,3,6,

我可以消除收益率?我知道支持這件事的管道是不平凡的,而且可能有很大的成本。在linq中可以做到這一切嗎?

我覺得我非常接近答案,只是缺少關鍵字來搜索。 :)

UPDATE:

正如下面的答覆中提到,它的工作原理使用是這樣的:

foos.SelectMany(x => new[] { x.X, x.Y }); 

不過,我希望能找到一個方法來做到這一點,而不會產生N/2個臨時陣列。我正在針對大型選擇集運行此操作。

+1

這有什麼錯產量的解決方案? – 2010-01-29 23:46:32

+0

我讀過一篇文章,談論如何在下面的收益率,它不便宜。我還想在工作的完成位置旁邊放置一些內容,而不是要求完全獨立的功能。 Lambdas ftw。 – scobi 2010-02-02 15:50:22

+0

「不便宜」從哪裏來?你有鏈接到文章? – 2010-02-03 10:00:02

回答

1

好吧,如果你想避免臨時數組創造,但你想使用LINQ短和漂亮的代碼,你可以去用 -

var query = foos.Aggregate(
    new List<int>(), 
    (acc, x) => { acc.Add(x.X); acc.Add(x.Y); return acc; } 
    ); 
+0

是的!這是我一直在尋找的東西。 – scobi 2010-02-02 15:55:40

1

你可以這樣做:

var query = foos.SelectMany(x => new[] { x.X, x.Y }); 
+0

糟糕 - 我忘了提及我不想這樣做。試圖避免臨時陣列的創建。將更新我的問題。 – scobi 2010-01-29 22:56:39

0

這種挫折IEnumerable<T>的,更比得上我們與PushLINQ做 - 但它是一個很大比在飛行實施迭代器塊簡單(通過IL),同時通過動態方法保持致盲性能;使用object是如果你的數據是非正交的,你通過相同的API需要多種類型:

using System; 
using System.Reflection; 
using System.Reflection.Emit; 

// the type we want to iterate efficiently without hard code 
class Foo 
{ 
    public int X, Y; 
} 
// what we want to do with each item of data 
class DemoPusher : IPusher<int> 
{ 
    public void Push(int value) 
    { 
     Console.WriteLine(value); 
    } 
} 
// interface for the above implementation 
interface IPusher<T> 
{ 
    void Push(T value); 
} 
static class Program 
{ 
    // see it working 
    static void Main() 
    { 
     Foo foo = new Foo { X = 1, Y = 2 }; 
     var target = new DemoPusher(); 
     var pushMethod = CreatePusher<int>(typeof(Foo)); 
     pushMethod(foo, target);  
    } 
    // here be dragons 
    static Action<object, IPusher<T>> CreatePusher<T>(Type source) 
    { 
     DynamicMethod method = new DynamicMethod("pusher", 
      typeof(void), new[] { typeof(object), typeof(IPusher<T>) }, source); 
     var il = method.GetILGenerator(); 
     var loc = il.DeclareLocal(source); 
     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Castclass, source); 
     il.Emit(OpCodes.Stloc, loc); 
     MethodInfo push = typeof(IPusher<T>).GetMethod("Push"); 
     foreach (var field in source.GetFields(BindingFlags.Instance 
      | BindingFlags.Public | BindingFlags.NonPublic)) 
     { 
      if (field.FieldType != typeof(T)) continue; 
      il.Emit(OpCodes.Ldarg_1); 
      il.Emit(OpCodes.Ldloc, loc); 
      il.Emit(OpCodes.Ldfld, field); 
      il.EmitCall(OpCodes.Callvirt, push, null); 
     } 
     il.Emit(OpCodes.Ret); 
     return (Action<object, IPusher<T>>) 
      method.CreateDelegate(typeof(Action<object, IPusher<T>>)); 
    } 

} 
2

如果你擔心編譯器弄虛作假涉及yield和/或成本費用的SelectMany,你可以嘗試通過不調用Flatten每個Foo而是Flatten,以儘量減少這些影響的foos直接:

public class MyClass 
{ 
    class Foo 
    { 
     public int X, Y; 
    } 

    static IEnumerable<int> Flatten(IEnumerable<Foo> foos) 
    { 
     foreach (var foo in foos) 
     { 
      yield return foo.X; 
      yield return foo.Y; 
     } 
    } 

    public static void RunSnippet() 
    { 
     var foos = new List<Foo>() 
     { 
      new Foo() { X = 1, Y = 2 }, 
      new Foo() { X = 2, Y = 4 }, 
      new Foo() { X = 3, Y = 6 }, 
     }; 

     var query = Flatten(foos); 

     foreach (var x in query) 
     { 
      Console.WriteLine(x); 
     } 
    } 
} 

我碰到這個小測試應用程序,我已經看到有一些pe第二次實施帶來了性能優勢。在我的機器上,使用兩種算法壓扁100,000 Foo分別花費36ms和13ms。一如既往YMMV。