2009-05-21 15 views
10

Amo C#, amo il framework e mi piace anche imparare il più possibile. Oggi ho iniziato a leggere articoli su LINQ in C# e non ho trovato nulla di buono per un principiante che non ha mai lavorato con SQL nella sua vita.LINQ per principianti

Ho trovato l'articolo this molto utile e ne ho compreso piccole parti, ma mi piacerebbe avere più esempi.

Dopo averlo letto un paio di volte, ho provato a utilizzare LINQ in una funzione mia, ma non sono riuscito.

private void Filter(string filename) 
    { 
     using (TextWriter writer = File.CreateText(Application.StartupPath + "\\temp\\test.txt")) 
     { 
      using(TextReader reader = File.OpenText(filename)) 
      { 
       string line; 
       while((line = reader.ReadLine()) != null) 
       { 
        string[] items = line.Split('\t'); 
        int myInteger = int.Parse(items[1]); 
        if (myInteger == 24809) writer.WriteLine(line); 
       } 
      } 
     } 
    } 

Questo è quello che ho fatto e non ha funzionato, il risultato è stato sempre falso.

private void Filter(string filename) 
    { 
     using (TextWriter writer = File.CreateText(Application.StartupPath + "\\temp\\test.txt")) 
     { 
      using(TextReader reader = File.OpenText(filename)) 
      { 
       string line; 
       while((line = reader.ReadLine()) != null) 
       { 
        string[] items = line.Split('\t'); 
        var Linqi = from item in items 
           where int.Parse(items[1]) == 24809 
           select true; 
        if (Linqi == true) writer.WriteLine(line); 
       } 
      } 
     } 
    } 

sto chiedendo due cose:

  1. Come sarebbe la funzione simile utilizzando il più possibile Linq?
  2. Un sito/libro/articolo su Linq, ma per favore nota che sono un principiante decente in sql/linq.

Grazie in anticipo!

+0

Solo una nota. Il tuo "where int.Parse (items [1]) == 24809" sta guardando il secondo carattere in ciascuna colonna per ogni riga. –

risposta

19

Bene una cosa che renderebbe il tuo campione più "LINQy" è un IEnumerable<string> per leggere le righe da un file. Ecco una versione un po 'semplificata della mia classe LineReader da MiscUtil:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.IO; 

public sealed class LineReader : IEnumerable<string> 
{ 
    readonly Func<TextReader> dataSource; 

    public LineReader(string filename) 
     : this(() => File.OpenText(filename)) 
    { 
    } 

    public LineReader(Func<TextReader> dataSource) 
    { 
     this.dataSource = dataSource; 
    } 

    public IEnumerator<string> GetEnumerator() 
    { 
     using (TextReader reader = dataSource()) 
     { 
      string line; 
      while ((line = reader.ReadLine()) != null) 
      { 
       yield return line; 
      } 
     } 
    } 


    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Ora è possibile utilizzare tale:

var query = from line in new LineReader(filename) 
       let items = line.Split('\t') 
       let myInteger int.Parse(items[1]); 
       where myInteger == 24809 
       select line; 

    using (TextWriter writer = File.CreateText(Application.StartupPath 
               + "\\temp\\test.txt")) 
    { 
     foreach (string line in query) 
     { 
      writer.WriteLine(line); 
     } 
    } 

Nota che probabilmente sarebbe più efficiente di non avere le let clausole:

var query = from line in new LineReader(filename) 
       where int.Parse(line.Split('\t')[1]) == 24809 
       select line; 

a quel punto si potrebbe ragionevolmente fare tutto in "dot notation":

var query = new LineReader(filename) 
         .Where(line => int.Parse(line.Split('\t')[1]) == 24809); 

Tuttavia, preferisco di gran lunga la leggibilità della query originale :)

+1

@ Jon, ho il tuo libro, è davvero fantastico avere un'opera d'arte. Ho un'altra domanda. Potresti dirmi la pagina in cui spieghi dettagliatamente il LINQ nel tuo libro? –

+0

Il tuo troppo dannatamente veloce Jon. :) –

+0

In una nota simile, ecco il post sul blog che rende enumerabili gli Stream: http://www.atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/making-streams-enumerable.aspx – plinth

6

101 LINQ Samples è certamente una buona collezione di esempi. Anche LINQPad potrebbe essere un buon modo per giocare con LINQ.

-1

non si può solo controllare se Linqi è vero ... Linqi è un IEnumerable<bool> (in questo caso) quindi è necessario verificare come Linqi.First() == true

ecco un piccolo esempio:

string[] items = { "12121", "2222", "24809", "23445", "24809" }; 

         var Linqi = from item in items 
            where Convert.ToInt32(item) == 24809 
            select true; 
         if (Linqi.First() == true) Console.WriteLine("Got a true"); 

Si potrebbe anche iterare Linqi, e nel mio esempio ci sono 2 articoli della collezione.

+0

Un esempio di utilizzo del maggior numero di linq possibile nella mia funzione sarà molto apprezzato. :) –

+0

amore quando non c'è una spiegazione per un voto negativo ... che dovrebbe essere un requisito. – CSharpAtl

+0

poiché la domanda riguardava SQL e LINQ non ho provato a riscrivere completamente il suo codice. – CSharpAtl

0

Se dovessi riscrivere la funzione di filtro utilizzando LINQ, ove possibile, che sarebbe simile a questa:

private void Filter(string filename) 
{ 
    using (TextWriter writer = File.CreateText(Application.StartupPath + "\\temp\\test.txt")) 
    { 
     var lines = File.ReadAllLines(filename); 
     var matches = from line in lines 
         let items = line.Split('\t') 
         let myInteger = int.Parse(items[1]); 
         where myInteger == 24809 
         select line; 

     foreach (var match in matches) 
     { 
      writer.WriteLine(line) 
     } 
    } 
} 
+0

Hmm ... sembra familiare :) (anche se non sono sicuro del motivo per cui stai selezionando l'int quando vuoi stampare la linea) –

+0

Nota che leggere tutte le righe in un colpo solo è un fattore limitante - e inutilmente così. (Vedi la mia risposta :) –

+0

È un problema per i file di grandi dimensioni, ovviamente. La risposta di Jon Skeet è migliore ... sospiro, cos'altro è nuovo. :-) –

0

Per rispondere alla prima questione, francamente non è troppo ragione per usare LINQ il modo suggerisci nella funzione sopra, tranne come esercizio. In effetti, probabilmente rende la funzione più difficile da leggere.

LINQ è più utile per operare su una raccolta di un singolo elemento, e invece lo userei in questo modo. Quindi, ecco il mio tentativo di utilizzare il più LINQ possibile nella funzione (fare alcuna menzione di efficienza e Non suggerisco di leggere l'intero file nella memoria come questo):

private void Filter(string filename) 
{ 
    using (TextWriter writer = File.CreateText(Application.StartupPath + "\\temp\\test.txt")) 
    { 
     using(TextReader reader = File.OpenText(filename)) 
     { 
      List<string> lines; 
      string line; 
      while((line = reader.ReadLine()) != null) 
       lines.Add(line); 

      var query = from l in lines 
         let splitLine = l.Split('\t') 
         where int.Parse(splitLine.Skip(1).First()) == 24809 
         select l; 

      foreach(var l in query)    
       writer.WriteLine(l); 
     } 
    } 
} 
1

primo luogo, vorrei introdurre questo metodo:

private IEnumerable<string> ReadLines(StreamReader reader) 
{ 
    while(!reader.EndOfStream) 
    { 
     yield return reader.ReadLine(); 
    } 
} 

Poi, vorrei refactoring il metodo principale di usarlo. Ho messo entrambe le using dichiarazioni sopra lo stesso blocco, e anche aggiunto un test di ricezione per garantire items[1] non manca:

private void Filter(string fileName) 
{ 
    using(var writer = File.CreateText(Application.StartupPath + "\\temp\\test.txt")) 
    using(var reader = File.OpenText(filename)) 
    { 
     var myIntegers = 
      from line in ReadLines(reader) 
      let items = line.Split('\t') 
      where items.Length > 1 
      let myInteger = Int32.Parse(items[1]) 
      where myInteger == 24809 
      select myInteger; 

     foreach(var myInteger in myIntegers) 
     { 
      writer.WriteLine(myInteger); 
     } 
    } 
} 
+0

In che modo non dispone di TextReader? È in una dichiarazione usando. –

+0

(Si noti che * non lo * smaltirà se un chiamante chiama manualmente MoveNext() e quindi abbandona l'iteratore, ma consente di chiamare automaticamente Dispose.) –

+0

Hai ragione, ho letto male. Non stai utilizzando la funzione di costruzione che accetta Func . Intendevo dire che quando si utilizza quella funzione, la classe LineReader non incapsula completamente la durata dell'istanza TextReader. –

1

Per quanto riguarda i libri Linq, mi raccomando:

http://www.ebookpdf.net/screen/cover2/51n4fa2xxvl_083.jpg     http://www.diesel-ebooks.com/mas_assets/full/0321564189.jpg

Entrambi sono ottimi libri che punta in Linq in dettaglio.

Per aggiungere ancora un'altra variazione al tema come-tanto-LINQ-come-possibile, ecco il mio prendere:

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

namespace LinqDemo 
{ 
    class Program 
    { 
     static void Main() 
     { 
      var baseDir = AppDomain.CurrentDomain.BaseDirectory; 
      File.WriteAllLines(
       Path.Combine(baseDir, "out.txt"), 
       File.ReadAllLines(Path.Combine(baseDir, "in.txt")) 
        .Select(line => new KeyValuePair<string, string[]>(line, line.Split(','))) // split each line into columns, also carry the original line forward 
        .Where(info => info.Value.Length > 1) // filter out lines that don't have 2nd column 
        .Select(info => new KeyValuePair<string, int>(info.Key, int.Parse(info.Value[1]))) // convert 2nd column to int, still carrying the original line forward 
        .Where(info => info.Value == 24809) // apply the filtering criteria 
        .Select(info => info.Key) // restore original lines 
        .ToArray()); 
     } 
    } 
} 

Nota che ho cambiato i tuoi delimitati da tabulazioni colonne alle colonne delimitati da virgole (più facile per l'autore nel mio editor che converte le schede in spazi ;-)). Quando questo programma viene eseguito un file di input:

A1,2 
B,24809,C 
C 

E 
G,24809 

l'uscita sarà:

B,24809,C 
G,24809 

si potrebbe migliorare i requisiti di memoria di questa soluzione, sostituendo "File.ReadAllLines" e "File.WriteAllLines" con LineReader di Jon Skeet (e LineWriter in modo simile, prendendo IEnumerable e scrivendo ogni elemento restituito nel file di output come una nuova riga).Ciò trasformerebbe la soluzione di cui sopra da "ottenere tutte le linee in memoria come una matrice, filtrarle, creare un'altra matrice in memoria per il risultato e scrivere questo risultato sul file di output" a "leggere le righe dal file di input una alla volta, e se linea soddisfa i nostri criteri, scriverlo immediatamente nel file di output "(approccio pipeline).

1

Ho trovato questo articolo di essere estremamente cruciale capire LINQ che si basa su tanti nuovi costrutti introdotte in .NET 3.0 & 3.5:

Ti avverto è una lunga lettura, ma se davvero voglio capire che cosa Linq è e fa credo che sia essenziale

http://blogs.msdn.com/ericwhite/pages/FP-Tutorial.aspx

Buona lettura