2010-03-12 13 views
5

AKTUALIZACJA: Powinienem był wspomnieć w oryginalnym poście, że chcę dowiedzieć się więcej o generycznych tutaj. Mam świadomość, że można to zrobić, modyfikując klasę podstawową lub tworząc interfejs, który implementują obie klasy dokumentów. Jednak w trosce o to ćwiczenie interesują mnie rozwiązania, które nie wymagają modyfikacji klas dokumentów ani ich klasy bazowej. Sądziłem, że fakt, że to pytanie dotyczy metod rozszerzenia, oznaczałby to.Jak zmienić te ogólne metody?

Napisałem dwie prawie identyczne ogólne metody rozszerzenia i próbuję ustalić, w jaki sposób mogę je przekształcić w jedną metodę. Różnią się tylko tym, że działają na liście, a inne na liście, a właściwości, które mnie interesują, to AssetID dla AssetDocument i PersonID dla PersonDocument. Chociaż AssetDocument i PersonDocument mają tę samą klasę podstawową, właściwości są zdefiniowane w każdej klasie, więc nie sądzę, że to pomaga. Próbowałem

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

myślenia I może być w stanie przetestować rodzaj i podjąć odpowiednie działania, ale to powoduje błąd składni

Typ parametru „T” dziedziczy sprzeczne ograniczenia

Są to metody, które chciałbym przekształcić w jedną metodę, ale być może po prostu odchodzę za burtę i najlepiej byłoby pozostawić takie, jakie są. Chciałbym usłyszeć, co myślisz.

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

zrobić AssetDocument i PersonDocument pochodzą ze wspólnej klasy bazowej/interfejsu? – Preets

Odpowiedz

7

Twoja implementacja polega zasadniczo na ponownym wprowadzeniu łańcucha znaków.Dołącz metodę, więc możesz spróbować zrobić to prostsze i bardziej rodzajowe z pewnym 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, przeoczyłem bardziej oczywiste string.Join :-( –

+0

Nie jesteś sam :-) – TToni

+0

Podoba mi się to rozwiązanie. Nie zmienia kodu wywołującego i redukuje duplikat kodu do niezbędnego minimum. Używam LINQ całkiem sporo, ale naprawdę muszę pamiętać, aby upewnić się, że nie ma metody LINQ przed wyjazdem i napisaniem własnego do zrobienia czegoś. –

3

Myślę, że rozwiązaniem byłoby pozwolić PersonDocument i AssetDocument dziedziczyć z klasy dokument, który miałby właściwość ID, który przechowuje aktualną PersonID lub AssetId respectivly.

+0

To też jest dobre, ponieważ ma już klasę podstawową. Nawet jeśli właściwość jest zadeklarowana w klasie bazowej, obie podklasy mogą tworzyć własne jej implementacje. –

+0

Dobra odpowiedź, ale zapoznaj się z moją aktualizacją powyżej. –

3

Bądź abstrakcją, takich jak IDocument lub klasą abstrakcyjną BaseDocument co naraża identyfikator (który jest tylko pole, które są naprawdę użyciem) i dokonać zarówno PersonDocument i AssetDocument wdrożenia tego. Teraz zmień metodę ogólną na akceptującą IDocument lub BaseDocument.

+0

Chciałem to również zasugerować. –

+0

Dobra odpowiedź, ale zapoznaj się z moją aktualizacją powyżej. –

1

wiem tylko Java, więc nie mogę podać prawidłową składnię, ale ogólne podejście powinno działać:

zdefiniować Document Interface, który zostanie wdrożony przez PersonDocument i AssetDocument, metodą

String getIdString(); 

Użyj Listy jako parametru dla metody. Zauważ, że jest to składnia Java dla Listy Coś, która dziedziczy/rozciąga się od Dokumentu.

+0

Dobra odpowiedź, ale zapoznaj się z moją aktualizacją powyżej. –

2

Jak Ci się podoba ten wariant (nieco uproszczone, ale powinny masz pomysł):

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

to będzie działać z każdym liście (w przypadku, gdy nie dbają o zdefiniowanej przez pamięci w StringBuilder nawet z dowolnymi IEnumerable).

Aktualizacja: Nawet jeśli chcesz zachować swoje pierwotne metody rozszerzenia, możesz zredukować je do jednego wiersza kodu.

+0

Możliwe do wykonania, ale przenoszenie większej złożoności na numer wywołujący, aby zapisać kilka zduplikowanych linii kodu, nie ma większego sensu. –

2

co czyni metoda ma również delegatem do zwrotu document.AssetID.ToString() dla tej listy jako właściwe?

Używanie wyrażeń Lamda może być rozsądnie lekkie, jeśli trochę brzydkie. Aplikacja konsolowa do demonstaracji:

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

Niezależnie od tego, czy jest to najlepsze podejście, czy nie, zostawiam jako ćwiczenie dla czytelnika!

+0

Możliwe do wykonania, ale przenoszenie większej złożoności na numer wywołujący, aby zapisać kilka zduplikowanych linii kodu, nie ma większego sensu. –

+0

Całkowicie się zgadzam - stąd mój ostateczny komentarz. Choć dodaje trochę elastyczności, nie mogę sobie wyobrazić, że byłoby to przydatne :) –

1

Możesz użyć Odbicia dla odrobiny akcji Duck Typing!

Założono, że twoje klasy są nazywane # class # Document i chcesz połączyć właściwości # class # ID. Jeśli lista zawiera klasy zgodne z tą nazwą, zostaną one połączone. W przeciwnym razie nie będą.

W ten sposób działa szkielet Rails, używając Convention over Configuration.

Oczywiście takie zachowanie jest bardziej odpowiednie dla dynamicznych języków, takich jak Ruby. Prawdopodobnie najlepszym rozwiązaniem dla bardziej statycznego języka, takiego jak C#, byłoby refaktoryzowanie klas bazowych, używanie interfejsów itp. Ale to nie było w specyfikacji, a dla celów edukacyjnych jest to jeden sposób na wszystko!

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 (uwaga nie ma potrzeby dziedziczenia z Duck Typing - działa również z każdego rodzaju!):

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

...