2010-03-12 12 views
5

ACTUALIZACIÓN: Debería haber mencionado en la publicación original que quiero aprender más sobre los genéricos aquí. Soy consciente de que esto se puede hacer modificando la clase base o creando una interfaz que implementen ambas clases de documentos. Pero por el bien de este ejercicio, solo estoy realmente interesado en las soluciones que no requieren ninguna modificación a las clases de documento o su clase base. Pensé que el hecho de que la pregunta implique métodos de extensión habría implicado esto.¿Cómo refactorizar estos métodos genéricos?

He escrito dos métodos de extensión genéricos casi idénticos y estoy tratando de averiguar cómo podría refactorizarlos en un único método. Se diferencian solo en que uno opera en Lista y el otro en Lista, y las propiedades que me interesan son AssetID para AssetDocument y PersonID para PersonDocument. Aunque AssetDocument y PersonDocument tienen la misma clase base, las propiedades están definidas en cada clase, así que no creo que eso ayude. He tratado

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

pensando que entonces podría ser capaz de probar el tipo y actuar en consecuencia, pero esto resulta en el error de sintaxis

parámetro tipo 'T' hereda restricciones conflictivas

Estos son los métodos que me gustaría refactorizar en un único método, pero tal vez simplemente me vaya por la borda y es mejor dejarlos tal como están. Me gustaría saber lo que piensas.

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

hacer AssetDocument y PersonDocument derivan de una clase base/interfaz común? – Preets

Respuesta

7

Su implementación es básicamente una cadena de reimplementación.Únete método, por lo que podría tratar de hacerlo lo más simple y más genérico con un poco de 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, me perdí la cadena más obvia. Únete :-( –

+0

No estás solo :-) – TToni

+0

Me gusta esta solución. No cambia el código de llamada y reduce el código duplicado al mínimo. Yo uso LINQ bastante pero realmente debo recordar asegurarme de que no haya un método LINQ antes de partir y escribir el mío para hacer algo. –

3

Creo que la forma sería dejar que PersonDocument y AssetDocument heredan de una clase de documento, que tendría una propiedad Id, que almacena su actual PERSONID o assetId respectivly.

+0

Esto también es bueno, porque él ya tiene una clase base. Incluso si la propiedad se declara en la clase base, ambas subclases pueden crear sus propias implementaciones de la misma. –

+0

Buena respuesta, pero por favor vea mi actualización anterior. –

3

Hacer una abstracción, como IDocument o una clase abstracta BaseDocument que expone el ID (que es el único campo que realmente está utilizando) y hacen tanto PersonDocument y AssetDocument implemento que. Ahora haga que su método genérico acepte IDocument o BaseDocument en su lugar.

+0

Iba a sugerir esto también. –

+0

Buena respuesta, pero por favor vea mi actualización más arriba. –

1

Sólo sé Java, por lo que no puede dar sintaxis correcta, pero el enfoque general debería funcionar:

definir un documento de interfaz, que se implementa por PersonDocument y AssetDocument, con el método

String getIdString(); 

Use una lista como parámetro para su método. Tenga en cuenta que esta es la sintaxis de Java para una Lista de algo que hereda/extiende desde el documento.

+0

Buena respuesta, pero por favor vea mi actualización más arriba. –

2

¿Qué le parece esta variante (un poco simplificada, pero se debe tener la 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(); 
     } 
    } 
} 

Esto funcionaría con cualquier lista (en caso de que no se preocupan por la memoria en preasignado StringBuilder incluso con cualquier IEnumerable).

Actualización: incluso si desea mantener sus métodos de extensión originales, puede reducirlos a una línea de código con esto.

+0

Funcionable pero moviendo más complejidad a la persona que llama para guardar unas pocas líneas duplicadas de código realmente no tiene sentido. –

2

Lo que trata de hacer que su método también toma en un delegado para devolver el document.AssetID.ToString() para esa lista en su caso?

El uso de expresiones Lamda podría ser razonablemente ligero, aunque un poco feo. Una aplicación de consola para 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(); 
    } 
} 

Si este es el mejor enfoque o no, lo dejo como ejercicio para el lector!

+0

No se puede usar, pero mover más complejidad a la persona que llama para guardar unas pocas líneas de código duplicadas. –

+0

Totalmente de acuerdo, de ahí mi comentario final. Aunque agrega cierta flexibilidad, no me puedo imaginar que sería útil :) –

1

Puede usar Reflection para un poco de acción Duck Typing!

He supuesto que sus clases se llaman # clase # Documento y desea concatenar las propiedades # ID de clase #. Si la lista contiene clases que se ajustan a esta denominación, se concatenarán. De lo contrario, no lo harán.

Así es como funciona la estructura Rails, usando Convention over Configuration.

Obviamente, tal comportamiento es más adecuado para lenguajes dinámicos como Ruby. Probablemente la mejor solución para un lenguaje más estático como C# sería refactorizar las clases base, usar interfaces, etc. Pero eso no estaba en la especificación, y para fines educativos, ¡esto es una forma de evitar las cosas!

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

Uso (obsérvese que no hay necesidad de herencia con Typing pato - también funciona con cualquier 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()); 

...

Cuestiones relacionadas