2 Stimmen

Wie man einen Typ (nicht eine aufzählbare) durch Linq flatten?

Alle Beispiele für SelectMany, die ich sehe, sind flattening Arrays von Arrays und so weiter. Ich habe einen anderen Blickwinkel auf diese Frage.

Ich habe ein Array eines Typs, und ich möchte den Inhalt dieses Typs in einen Stream extrahieren. Hier ist mein Beispielcode:

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);
        }
    }
}

Diese Ergebnisse würde ich gerne sehen: 1, 2, 2, 4, 3, 6.

Kann ich die Erträge eliminieren? Ich weiß, dass die dafür erforderlichen Installationen nicht trivial sind und wahrscheinlich erhebliche Kosten verursachen. Ist es möglich, es alle in Linq zu tun?

Ich habe das Gefühl, dass ich der Antwort sehr nahe bin und mir nur noch das magische Schlüsselwort fehlt, nach dem ich suchen muss :)

UPDATE:

Wie in der Antwort weiter unten erwähnt, kann man etwas in der Art verwenden:

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

Ich hatte jedoch gehofft, eine Möglichkeit zu finden, dies zu tun, ohne n/2 temporäre Arrays zu erzeugen. Ich lasse dies gegen eine große Auswahlmenge laufen.

2voto

Christoffer Lette Punkte 13576

Wenn Sie sich Sorgen über die Kosten der Compiler-Tricksereien machen, die mit yield und/oder die Kosten für SelectMany können Sie versuchen, die Auswirkungen zu minimieren, indem Sie keine Flatten an jedem Foo sondern stattdessen Flatten die foos direkt:

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);
        }
    }
}

Ich habe dazu eine kleine Testanwendung ausgeführt und festgestellt, dass die zweite Implementierung einige Leistungsvorteile bietet. Auf meinem Rechner werden bei der Verflachung von 100.000 Foo s mit beiden Algorithmen 36ms bzw. 13ms benötigt. Wie immer gilt: YMMV.

1voto

Mark Byers Punkte 761508

Sie könnten dies tun:

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

1voto

Martin Jonáš Punkte 2299

Nun, wenn Sie die Erstellung temporärer Arrays vermeiden möchten und dennoch kurzen und schönen Code mit LINQ wünschen, können Sie mit -

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

0voto

Marc Gravell Punkte 970173

Diese Art von Umkehrung IEnumerable<T> und ist eher vergleichbar mit dem, was wir mit PushLINQ gemacht haben - aber es ist eine Menge Einfacher als die Implementierung eines Iterator-Blocks im laufenden Betrieb (durch IL), wobei dank der dynamischen Methode eine erstaunliche Leistung beibehalten wird; die Verwendung von object ist für den Fall, dass Ihre Daten nicht orthogonal sind und Sie mehrere Typen über dieselbe API benötigen:

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>>));
    }

}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X