È buona pratica creare un ClassCollection di un'altra classe?

30

Diciamo che ho una classe Car :

public class Car
{
    public string Engine { get; set; }
    public string Seat { get; set; }
    public string Tires { get; set; }
}

Diciamo che stiamo creando un sistema per un parcheggio, useremo molto della classe Car , quindi creiamo una classe CarCollection , potrebbe avere alcuni metodi di annunci come FindCarByModel :

public class CarCollection
{
    public List<Car> Cars { get; set; }

    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

Se sto facendo una classe ParkingLot , qual è la migliore pratica?

Opzione 1:

public class ParkingLot
{
    public List<Car> Cars { get; set; }
    //some other properties
}

Opzione n. 2:

public class ParkingLot
{
    public CarCollection Cars { get; set; }
    //some other properties
}

È addirittura una buona pratica creare un ClassCollection di un altro Class ?

    
posta Luis 25.04.2013 - 18:45
fonte

5 risposte

34

Prima di generare i generici in .NET, era prassi comune creare collezioni "tipizzate" in modo da avere class CarCollection ecc. per ogni tipo che dovevi raggruppare. In .NET 2.0 con l'introduzione di Generics, è stata introdotta una nuova classe List<T> che ti risparmia la necessità di creare CarCollection ecc perché puoi creare List<Car> .

La maggior parte delle volte, troverai che List<T> è sufficiente per i tuoi scopi, tuttavia potrebbero esserci momenti in cui desideri avere un comportamento specifico nella tua raccolta, se ritieni che sia così, hai un paio delle opzioni:

  • Crea una classe che incapsula List<T> ad esempio public class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Crea una raccolta personalizzata public class CarCollection : CollectionBase<Car> {}

Se scegli l'approccio di incapsulamento, dovresti almeno esporre l'enumeratore in modo da dichiararlo come segue:

public class CarCollection : IEnumerable<Car>
{
    private List<Car> cars = new List<Car>();

    public IEnumerator<Car> GetEnumerator() { return this.cars.GetEnumerator(); }
}

Senza farlo, non puoi fare un foreach sulla collezione.

Alcuni motivi per cui potresti voler creare una raccolta personalizzata sono:

  • Non vuoi esporre completamente tutti i metodi in IList<T> o ICollection<T>
  • Vuoi eseguire ulteriori azioni aggiungendo o rimuovendo un oggetto dalla raccolta

È una buona pratica? beh ciò dipende da perché lo stai facendo, se è per esempio uno dei motivi che ho elencato sopra allora sì.

Microsoft lo fa abbastanza regolarmente, ecco alcuni esempi abbastanza recenti:

Come per i tuoi metodi FindBy , sarei tentato di inserirli nei metodi di estensione in modo che possano essere utilizzati contro qualsiasi raccolta contenente auto:

public static class CarLookupQueries
{
    public static Car FindByLicencePlate(this IEnumerable<Car> source, string licencePlate)
    {
        return source.SingleOrDefault(c => c.LicencePlate == licencePlate);
    }

    ...
}

Questo separa la preoccupazione di interrogare la collezione dalla classe che memorizza le macchine.

    
risposta data 25.04.2013 - 19:15
fonte
9

No. La creazione di classi XXXCollection ha praticamente perso il suo stile con l'avvento dei generici in .NET 2.0. In effetti, esiste l'estensione nifty Cast<T>() LINQ che la gente usa in questi giorni per estrarre materiale da quei formati personalizzati.

    
risposta data 25.04.2013 - 19:01
fonte
2

Spesso è utile disporre di metodi orientati al dominio per trovare / affettare le raccolte, come nell'esempio FindByCarModel riportato sopra, ma non è necessario ricorrere alla creazione di classi di raccolta wrapper. In questa situazione ora creerò tipicamente un insieme di metodi di estensione.

public static class CarExtensions
{
    public static IEnumerable<Car> ByModel(this IEnumerable<Car> cars, string model)
    {
        return cars.Where(car => car.Model == model);
    }
}

Puoi aggiungere tutti i metodi di filtro o di utilità a quella classe che desideri e puoi utilizzarli ovunque tu abbia IEnumerable<Car> , che include qualsiasi cosa ICollection<Car> , matrici di Car , IList<Car> ecc.

Poiché la nostra soluzione di persistenza ha un provider LINQ, creerò spesso anche metodi di filtro simili che operano e restituiscono IQueryable<T> , quindi possiamo applicare queste operazioni anche al repository.

L'idioma di .NET (beh, C #) è cambiato molto dal 1.1. Mantenere le classi di raccolta personalizzate è un problema e guadagni poco dall'assorbire da CollectionBase<T> che non ottieni con la soluzione del metodo di estensione se tutto ciò di cui hai bisogno è un metodo di filtro e selettore specifico per il dominio.

    
risposta data 26.04.2013 - 08:17
fonte
1

Penso che l'unica ragione per creare una classe speciale per tenere una collezione di altri elementi dovrebbe essere quando aggiungi qualcosa di valore ad esso, qualcosa di più che incapsulare / ereditare da un'istanza di IList o un altro tipo di raccolta.

Ad esempio, nel tuo caso, aggiungendo una funzione che restituirebbe sottoliste di macchine parcheggiate su spazi di lotto pari / dispari ... E anche allora ... forse solo se viene spesso riutilizzato, perché se prende solo una riga con una bella funzione LinQ e viene usata solo una volta, qual è il punto? KISS !

Ora, se prevedi di offrire molti metodi di ordinamento / ricerca, allora sì, penso che ciò potrebbe essere utile perché è qui che dovrebbero appartenere, in quella classe di raccolta speciale. Questo è anche un buon modo per "nascondere" le complessità di alcune query "find" o qualsiasi altra cosa tu possa fare in un metodo di ordinamento / ricerca.

    
risposta data 25.04.2013 - 19:03
fonte
-1

Preferisco utilizzare la seguente opzione, in modo da poter aggiungere il metodo alla raccolta e utilizzare il vantaggio dell'elenco.

public class CarCollection:List<Car>
{
    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

e quindi puoi usarlo come C # 7.0

public class ParkingLot
{
    public CarCollection Cars { get; set; }=new CarCollection();
    //some other properties
}

Oppure puoi usarlo come

public class ParkingLot
{
   public ParkingLot()
   {
      //initial set
      Cars =new CarCollection();
   }
    public CarCollection Cars { get; set; }
    //some other properties
}

- Versione generica grazie al commento @Bryan

   public class MyCollection<T>:List<T> where T:class,new()
    {
        public T FindOrNew(Predicate<T> predicate)
        {
            // code here
            return Find(predicate)?? new T();
        }
       //Other Common methods
     }

e quindi puoi usarlo

public class ParkingLot
{
    public MyCollection<Car> Cars { get; set; }=new MyCollection<Car>();
    public MyCollection<Motor> Motors{ get; set; }=new MyCollection<Motor>();
    public MyCollection<Bike> Bikes{ get; set; }=new MyCollection<Bike>();
    //some other properties
}
    
risposta data 27.04.2017 - 21:16
fonte