2009-09-23 21 views
30

Solo un piccolo inconveniente sulla sintassi LINQ. Sto appiattendo un IEnumerable<IEnumerable<T>> con SelectMany(x => x).Funzione identità LINQ?

Il mio problema è con l'espressione lambda x => x. Sembra un po 'brutto. C'è qualche oggetto statico di 'funzione di identità' che posso usare al posto di x => x? Qualcosa come SelectMany(IdentityFunction)?

+6

Non capirò mai perché non includono i combinatori SKI in C#. –

risposta

26

Nota: questa risposta è stata corretta per C# 3, ma ad un certo punto (C# 4 C# 5?) Inferenza di tipo migliorato in modo che il metodo IdentityFunction mostrato sotto può essere facilmente utilizzato.


No, non c'è. Avrebbe dovuto essere generico, per cominciare:

public static Func<T, T> IdentityFunction<T>() 
{ 
    return x => x; 
} 

Ma poi inferenza di tipo non avrebbe funzionato, in modo che ci si deve fare:

SelectMany(Helpers.IdentityFunction<Foo>()) 

che è molto più brutto di x => x.

Un'altra possibilità è che si avvolge questo in un metodo di estensione:

public static IEnumerable<T> Flatten<T> 
    (this IEnumerable<IEnumerable<T>> source) 
{ 
    return source.SelectMany(x => x); 
} 

Purtroppo con varianza generica così com'è, che potrebbe cadere fallo di vari casi in C# 3 ... non sarebbe essere applicabile a List<List<string>> per esempio. Si potrebbe rendere più generico:

public static IEnumerable<TElement> Flatten<TElement, TWrapper> 
    (this IEnumerable<TWrapper> source) where TWrapper : IEnumerable<TElement> 
{ 
    return source.SelectMany(x => x); 
} 

Ma ancora una volta, hai poi ottenuto problemi di inferenza di tipo, ho il sospetto ...

EDIT: Per rispondere alle osservazioni ... sì, C# 4 marche questo più facile. O meglio, si fa la prima Flatten metodo più utile di quanto non sia in C# 3. Ecco un esempio che funziona in C# 4, ma non funziona in C# 3 perché il compilatore non può convertire List<List<string>>-IEnumerable<IEnumerable<string>>:

using System; 
using System.Collections.Generic; 
using System.Linq; 

public static class Extensions 
{ 
    public static IEnumerable<T> Flatten<T> 
     (this IEnumerable<IEnumerable<T>> source) 
    { 
     return source.SelectMany(x => x); 
    } 
} 

class Test 
{ 
    static void Main() 
    { 
     List<List<string>> strings = new List<List<string>> 
     { 
      new List<string> { "x", "y", "z" }, 
      new List<string> { "0", "1", "2" } 
     }; 

     foreach (string x in strings.Flatten()) 
     { 
      Console.WriteLine(x); 
     } 
    } 
} 
+2

Grazie! Anche questo mi ha dato dei contatti interessanti. Penso che starò con x => x per il momento ... – Joe

+0

Ho provato a creare anche un metodo di estensione Flatten (che ho chiamato SelectMany ma l'obiettivo era lo stesso) ea causa del problema con la varianza non era t molto utile, avrei bisogno di molti sovraccarichi, quindi sono tornato a x => x ... Jon, sarebbe più facile fare un metodo di estensione di Flatten in C# 4/VB10? –

+0

@MetaKnight: modificherà il post ... –

1

È possibile avvicinarsi a ciò di cui si ha bisogno. Invece di una funzione statica normale, prendere in considerazione un metodo di estensione per la vostra IEnumerable<T>, come se la funzione identità è della collezione, non il tipo (una collezione in grado di generare la funzione identità dei suoi elementi):

public static Func<T, T> IdentityFunction<T>(this IEnumerable<T> enumerable) 
{ 
    return x => x; 
} 

con questo , non è necessario specificare il tipo di nuovo, e scrivere:

IEnumerable<IEnumerable<T>> deepList = ... ; 
var flat = deepList.SelectMany(deepList.IdentityFunction()); 

Questo si sente un po 'abusiva però, e probabilmente sarei andare con x=>x. Inoltre, non è possibile utilizzarlo in modo fluido (nel concatenamento), quindi non sarà sempre utile.

+0

È necessario restituire 'Func >' per farlo funzionare con 'SelectMany'. –

+1

È vero, funziona con 'Select' ma non riesce per' SelectMany' - non riesce a capire i tipi. vergogna. – Kobi

3

Funziona nel modo desiderato? Mi rendo conto che Jon ha pubblicato una versione di questa soluzione, ma ha un secondo parametro di tipo che è necessario solo se il tipo di sequenza risultante è diverso dal tipo di sequenza sorgente.

public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source) 
    where T : IEnumerable<T> 
{ 
    return source.SelectMany(item => item); 
} 
24

A meno che non fraintendere la questione, il seguente sembra funzionare bene per me in C# 4:

public static class Defines 
{ 
    public static T Identity<T>(T pValue) 
    { 
     return pValue; 
    } 

    ... 

È quindi possibile effettuare le seguenti operazioni nel tuo esempio:

var result = 
    enumerableOfEnumerables 
    .SelectMany(Defines.Identity); 

Come bene come usare Defines.Identity ovunque tu usi una lambda che assomiglia a x => x.

+3

So che questa è stata una presentazione tardiva, ma ho davvero la sensazione che risponda meglio alla domanda. – Anthony

1

Con C# 6.0 le cose stanno migliorando. Siamo in grado di definire la funzione di identità nel modo suggerito da @Sahuagin:

static class Functions 
{ 
    public static T It<T>(T item) => item; 
} 

e poi utilizzarlo in SelectMany la using static costruttore:

using Functions; 

... 

var result = enumerableOfEnumerables.SelectMany(It); 

penso che sembra molto laconica in questo modo. Trovo anche la funzione Identity utile quando si costruisce dizionari:

class P 
{ 
    P(int id, string name) // sad, we are not getting Primary Constructors in C# 6.0 
    { 
     ID = id; 
     Name = id; 
    } 

    int ID { get; } 
    int Name { get; } 

    static void Main(string[] args) 
    { 
     var items = new[] { new P(1, "Jack"), new P(2, "Jill"), new P(3, "Peter") }; 
     var dict = items.ToDictionary(x => x.ID, It); 
    } 
} 
2

con C# 6.0 e se si fa riferimento FSharp.Core che si possono fare:

using static Microsoft.FSharp.Core.Operators 

E allora il vostro libero di fare:

SelectMany(Identity) 
0

Mi piacerebbe andare con una classe semplice con una singola proprietà statica e aggiungere il numero desiderato in linea

internal class IdentityFunction<TSource> 
    { 
     public static Func<TSource, TSource> Instance 
     { 
      get { return x => x; } 
     } 
    } 

    SelectMany(IdentityFunction<Foo>.Instance) 
Problemi correlati