2010-03-12 13 views
5

AGGIORNAMENTO: avrei dovuto menzionare nel post originale che voglio saperne di più sui generici qui. Sono consapevole che ciò può essere fatto modificando la classe base o creando un'interfaccia che entrambe le classi di documenti implementano. Ma per il piacere di questo esercizio sono interessato solo a soluzioni che non richiedono alcuna modifica alle classi di documenti o alla loro classe base. Pensavo che il fatto che la domanda riguardasse i metodi di estensione avrebbe implicato questo.Come refactoring questi metodi generici?

Ho scritto due metodi di estensione generici quasi identici e sto cercando di capire come potrei rifattenerli in un unico metodo. Differiscono solo in quello che opera su List e l'altro su List e le proprietà a cui sono interessato sono AssetID per AssetDocument e PersonID per PersonDocument. Sebbene AssetDocument e PersonDocument abbiano la stessa classe base, le proprietà sono definite in ogni classe, quindi non credo che questo aiuti. Ho provato

public static string ToCSVList<T>(this T list) where T : List<PersonDocument>, List<AssetDocument> 

pensare che potrei quindi in grado di verificare il tipo e agire di conseguenza, ma questo genera l'errore di sintassi

parametro Type 'T' eredita contrastanti vincoli

Questi sono i metodi che mi piacerebbe refactoring in un unico metodo, ma forse sto semplicemente esagerando e sarebbe meglio lasciarli così come sono. Mi piacerebbe sentire cosa ne pensi.

public static string ToCSVList<T>(this T list) where T : List<AssetDocument> 
{ 
    var sb = new StringBuilder(list.Count * 36 + list.Count); 
    string delimiter = String.Empty; 

    foreach (var document in list) 
    { 
    sb.Append(delimiter + document.AssetID.ToString()); 
    delimiter = ","; 
    } 

    return sb.ToString(); 
} 

public static string ToCSVList<T>(this T list) where T : List<PersonDocument> 
{ 
    var sb = new StringBuilder(list.Count * 36 + list.Count); 
    string delimiter = String.Empty; 

    foreach (var document in list) 
    { 
    sb.Append(delimiter + document.PersonID.ToString()); 
    delimiter = ","; 
    } 

    return sb.ToString(); 
} 
+0

fare AssetDocument e PersonDocument derivano da una classe/interfaccia di base comune? – Preets

risposta

7

L'implementazione si basa essenzialmente sul reimplementing di stringhe.metodo join, per cui si potrebbe cercare di rendere più semplice e più generico con alcuni LINQ:

public static string ToCSVList<T>(this IEnumerable<T> collection) 
{ return string.Join(",", collection.Select(x => x.ToString()).ToArray()); } 

public static string ToCSVList(this IEnumerable<AssetDocument> assets) 
{ return assets.Select(a => a.AssetID).ToCSVList(); } 

public static string ToCSVList(this IEnumerable<PersonDocument> persons) 
{ return persons.Select(p => p.PersonID).ToCSVList(); } 
+0

Doh, ho perso la stringa più evidente.Join :-( –

+0

Non sei solo :-) – TToni

+0

Mi piace questa soluzione. Non cambia il codice chiamante e riduce il codice duplicato al minimo. Uso LINQ parecchio, ma devo ricordare che non esiste un metodo LINQ prima di uscire e scrivere il mio per fare qualcosa. –

3

penso che il modo sarebbe quello di lasciare che PersonDocument e AssetDocument ereditano da una classe documento, che avrebbe una proprietà Id, che memorizza il vostro attuale PersonId o AssetId respectivly.

+0

Anche questo è buono, perché ha già una classe base. Anche se la proprietà è dichiarata nella classe base, entrambe le sottoclassi possono crearne le proprie implementazioni. –

+0

Buona risposta ma si prega di consultare il mio aggiornamento sopra. –

3

Fai astrazione, come ad esempio IDocument o una classe astratta BaseDocument che espone l'id (che è l'unico campo che si sta realmente utilizzando) e fanno entrambi PersonDocument e AssetDocument attuare tale. Ora il tuo metodo generico accetta invece IDocument o BaseDocument.

+0

Stavo per suggerire anche questo. –

+0

Buona risposta ma si prega di consultare il mio aggiornamento sopra. –

1

So solo Java, quindi non posso dare sintassi corretta, ma l'approccio generale dovrebbe funzionare:

definire un documento di interfaccia, che viene implementato da PersonDocument e AssetDocument, con il metodo

String getIdString(); 

Utilizzare un elenco come parametro per il proprio metodo. Nota: questa è la sintassi java per un elenco di qualcosa che eredita/estende dal documento.

+0

Buona risposta ma si prega di consultare il mio aggiornamento sopra. –

2

Come ti piace questa variante (un po 'semplificato, ma si dovrebbe ottenere l'idea):

using System; 
using System.Collections.Generic; 
using System.Text; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main() 
     { 
      var la = new List<AssetDocument> { new AssetDocument() {AssetID = 1} }; 

      var result = la.ToCSVList(l => l.AssetID.ToString()); 
     } 
    } 

    public class AssetDocument 
    { 
     public int AssetID { get; set; } 
    } 

    public static class GlobalExtensions 
    { 
     public static string ToCSVList<T>(this List<T> list, Func<T, string> propResolver) 
     { 
      var sb = new StringBuilder(list.Count * 36 + list.Count); 
      var delimiter = ""; 

      foreach (var document in list) 
      { 
       sb.Append(delimiter); 
       sb.Append(propResolver(document)); 
       delimiter = ","; 
      } 

      return sb.ToString(); 
     } 
    } 
} 

Questo potrebbe funzionare con qualsiasi elenco (nel caso in cui non si preoccupano la memoria preallocato in StringBuilder anche con qualsiasi oggetto IEnumerable).

Aggiornamento: anche se si desidera mantenere i metodi di estensione originali, è possibile ridurli a una riga di codice con questo.

+0

Lavorabile ma spostare una maggiore complessità nel chiamante per salvare poche righe di codice duplicate non ha molto senso. –

2

Che dire di rendere il vostro metodo anche prendere in un delegato per restituire la document.AssetID.ToString() per quella lista, se del caso?

Utilizzando le espressioni Lamda questo potrebbe essere ragionevolmente leggero, se un po 'brutto. Un'applicazione console per demonstarate:

class Program 
    { 
    static void Main(string[] args) 
    { 
     List<string> strings = new List<string> { "hello", "world", "this", "is", "my", "list" }; 
     List<DateTime> dates = new List<DateTime> { DateTime.Now, DateTime.MinValue, DateTime.MaxValue }; 

     Console.WriteLine(ToCSVList(strings, (string s) => { return s.Length.ToString(); })); 
     Console.WriteLine(ToCSVList(dates, (DateTime d) => { return d.ToString(); })); 

     Console.ReadLine(); 
    } 

    public static string ToCSVList<T, U>(T list, Func<U, String> f) where T : IList<U> 
    { 
     var sb = new StringBuilder(list.Count * 36 + list.Count); 
     string delimiter = String.Empty; 

     foreach (var document in list) 
     { 
      sb.Append(delimiter + f(document)); 
      delimiter = ","; 
     } 

     return sb.ToString(); 
    } 
} 

Se questo è l'approccio migliore o no, lascio come esercizio per il lettore!

+0

Lavorabile ma spostare una maggiore complessità nel chiamante per salvare poche righe di codice duplicate non ha molto senso. –

+0

Totalmente d'accordo - da qui il mio commento finale. Anche se aggiunge una certa flessibilità, non posso immaginare che sarebbe utile :) –

1

È possibile utilizzare Reflection per un po 'di azione Duck Typing!

Ho assunto che le tue classi sono chiamate # class # Document e vuoi concatenare le proprietà # di # class ID. Se l'elenco contiene classi conformi a questa denominazione, verranno concatenati. Altrimenti non lo faranno.

Questo è il modo in cui funziona il framework Rails, utilizzando Convention over Configuration.

Ovviamente tale comportamento è più adatto a linguaggi dinamici come Ruby. Probabilmente la soluzione migliore per un linguaggio più statico come C# sarebbe il refactoring delle classi di base, l'uso di interfacce ecc. Ma questo non era nelle specifiche, e per scopi didattici questo è un modo per aggirare le cose!

public static class Extensions 
{ 
    public static string ToCSVList<T> (this T list) where T : IList 
    { 
     var sb = new StringBuilder (list.Count * 36 + list.Count); 
     string delimiter = String.Empty; 

     foreach (var document in list) 
     { 
      string propertyName = document.GetType().Name.Replace("Document", "ID"); 
      PropertyInfo property = document.GetType().GetProperty (propertyName); 
      if (property != null) 
      { 
       string value = property.GetValue (document, null).ToString(); 

       sb.Append (delimiter + value); 
       delimiter = ","; 
      } 
     } 

     return sb.ToString(); 
    } 
} 

Usage (nota senza bisogno di eredità con anatra Typing - funziona anche con qualsiasi tipo!):

public class GroovyDocument 
{ 
    public string GroovyID 
    { 
     get; 
     set; 
    } 
} 

public class AssetDocument 
{ 
    public int AssetID 
    { 
     get; 
     set; 
    } 
} 

...

 List<AssetDocument> docs = new List<AssetDocument>(); 
     docs.Add (new AssetDocument() { AssetID = 3 }); 
     docs.Add (new AssetDocument() { AssetID = 8 }); 
     docs.Add (new AssetDocument() { AssetID = 10 }); 

     MessageBox.Show (docs.ToCSVList()); 

     List<GroovyDocument> rocs = new List<GroovyDocument>(); 
     rocs.Add (new GroovyDocument() { GroovyID = "yay" }); 
     rocs.Add (new GroovyDocument() { GroovyID = "boo" }); 
     rocs.Add (new GroovyDocument() { GroovyID = "hurrah" }); 

     MessageBox.Show (rocs.ToCSVList()); 

...