36

Lets says I have a Carclass:

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

Lets say we're making a system about a parking lot, I'm going to use a lot of the Car class, so we make a CarCollection class, it may has a few aditionals methods like FindCarByModel:

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

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

If I'm making a class ParkingLot, what's the best practice?

Option #1:

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

Option #2:

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

Is it even a good practice to create a ClassCollection of another Class?

5
  • What benefit do you perceive in passing a CarCollection rather than a List<Car> around? Especially given that CarCollection doesn't extend the backing List class, or even implement the Collection interface (I'm sure that C# has similar things).
    – user40980
    Commented Apr 25, 2013 at 16:50
  • List<T> already implements IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T> and IEnumerable... Besides, I could use Linq...
    – Luis
    Commented Apr 25, 2013 at 17:01
  • But public class CarCollection doesn't implement IList or ICollection, etc... thus you can't pass it to something that is ok with a list. It claims as part of its name that it is a collection, but doesn't implement any of those methods.
    – user40980
    Commented Apr 25, 2013 at 17:16
  • 2
    being a 6 year old question I see no-one has mentioned that this is a common practice in DDD. Any collection should be abstracted into a custom collection. For example say you wanted to calculate the value of a group of cars. Where would you put that logic? in a service? Or in DDD you would have a CarColection with a TotalTradeValue property on it. DDD isn't the only way to design systems, just pointing it out as an option. Commented Apr 29, 2019 at 20:30
  • 1
    Exactly, These things are actually aggregate roots as suggested in DDD, Only thing is DDD would probably not recommend you to call it a car collection. Rather use some names like Parking Lot, or Work Area etc. Commented Jul 4, 2019 at 6:29

5 Answers 5

43

Prior to generics in .NET, it was common practice to create 'typed' collections so you would have class CarCollection etc for every type you needed to group. In .NET 2.0 with the introduction of Generics, a new class List<T> was introduced which saves you having to create CarCollection etc as you can create List<Car>.

Most of the time, you will find that List<T> is sufficient for your purposes, however there may be times that you want to have specific behaviour in your collection, if you believe this to be the case, you have a couple of options:

  • Create a class which encapsulates List<T> for example public class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Create a custom collection public class CarCollection : CollectionBase<Car> {}

If you go for the encapsulation approach, you should at minimum expose the enumerator so you would declare it as follows:

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

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

Without doing that, you can't do a foreach over the collection.

Some reasons you might want to create a custom collection are:

  • You don't want to fully expose all the methods in IList<T> or ICollection<T>
  • You want to perform additional actions upon adding or removing an item from the collection

Is it good practice? well that depends on why you are doing it, if it is for example one of the reasons I have listed above then yes.

Microsoft do it quite regularly, here are some fairly recent examples:

As for your FindBy methods, I would be tempted to put them in extension methods so that they can be used against any collection containing cars:

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

    ...
}

This separates the concern of querying the collection from the class which stores the cars.

3
  • Following this approach, I could even dismiss the ClassCollection even for the add, delete, update methods, by adding a new CarCRUD which will encapsulate all this methods...
    – Luis
    Commented Apr 25, 2013 at 17:22
  • @Luis I wouldn't recommend the CarCRUD extension as enforcing its use would be difficult, the advantage to putting the custom crud logic in the collection class is that there is no way to bypass it. Additionally, you may not actually care about the Find logic in the core assembly where Car etc are declared, that might be a UI only activity. Commented Apr 25, 2013 at 17:31
  • Just as a note from MSDN: "We don't recommend that you use the CollectionBase class for new development. Instead, we recommend that you use the generic Collection<T> class." - docs.microsoft.com/en-us/dotnet/api/…
    – Ryan
    Commented Jul 17, 2019 at 20:15
9

No. The creation of XXXCollection classes pretty much fell out of style with the advent of generics in .NET 2.0. In fact, there is the nifty Cast<T>() LINQ extension that folks use these days to get stuff out of those custom formats.

2
  • 1
    What about the custom methods we could have inside that ClassCollection? is it a good practice to put them on the main Class?
    – Luis
    Commented Apr 25, 2013 at 17:03
  • 3
    I believe that falls under the software development mantra of "it depends". If you're talking about, for example, your FindCarByModel method, that makes sense as a method on your repository, which is slightly more complex than just a Car collection. Commented Apr 25, 2013 at 17:12
2

It's often handy to have domain-oriented methods to find / slice collections, as in the FindByCarModel example above, but there's no need to resort to creating wrapper collection classes. In this situation I will now typically create a set of extension methods.

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

You add as many filter or utility methods to that class as you like, and you can use them anywhere you have IEnumerable<Car>, which includes anything ICollection<Car>, arrays of Car, IList<Car> etc.

Since our persistence solution has a LINQ provider, I'll frequently also create similar filter methods that operate on and return IQueryable<T>, so we can apply these operations to the repository too.

The idiom of .NET (well, C#) has changed a lot since 1.1. Maintaining custom collection classes is a pain, and you gain little from inheriting from CollectionBase<T> that you don't get with the extension method solution if all you need is domain-specific filter and selector methods.

1

I think that the only reason to create a special class for holding a collection of other items should be when you add something of value to it, something more than just encapsulate/inherit from an instance of IList or another type of collection.

For example, in your case, adding a function that would return sublists of cars parked on even/uneven lot spaces... And even then... maybe only if it's often reused, because if it only takes one line with a nice LinQ function and is used only once, what's the point ? KISS !

Now, if you plan to offer a lot of sorting/finding methods, then yes, I think this could be useful because this is where they should belong, in that special collection class. This is also a good way for "hiding" the complexities of some "find" queries or whatever you could do in a sorting/finding method.

2
  • Even tho, I think I could include those methods on the main Class
    – Luis
    Commented Apr 25, 2013 at 17:08
  • Yes, indeed... you can
    – Jalayn
    Commented Apr 25, 2013 at 17:10
-1

I prefer to go with the following option, so you can add you method to the collection and use the advantage of list.

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

and then you can use it like C#7.0

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

Or you can use it like

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

-- Generic version thanks to @Bryan comment

   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
     }

and then you can use it

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
}
4
  • don't inherit from List<T>
    – Bryan B
    Commented Apr 27, 2017 at 21:25
  • @Bryan, you right the question is not for generic collection. I'll modify my answer for generic collection. Commented Apr 28, 2017 at 12:16
  • 1
    @WaleedAK you still did it -- don't inherit from List<T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
    – Bryan B
    Commented Apr 28, 2017 at 14:42
  • @Bryan: if you read your link When is it acceptable? When you're building a mechanism that extends the List<T> mechanism., So it will be fine as long as there are no additional properties Commented Apr 28, 2017 at 17:48

Not the answer you're looking for? Browse other questions tagged or ask your own question.