C#: Encapsulation of for example collections

2019-03-21 16:04发布

I am wondering which one of these would be considered the cleanest or best to use and why.

One of them exposes the a list of passengers, which let the user add and remove etc. The other hides the list and only let the user enumerate them and add using a special method.

Example 1

class Bus
{
    public IEnumerable<Person> Passengers { get { return passengers; } }
    private List<Passengers> passengers;

    public Bus()
    {
        passengers = new List<Passenger>();
    }

    public void AddPassenger(Passenger passenger)
    {
        passengers.Add(passenger);
    }
}

var bus = new Bus1();
bus.AddPassenger(new Passenger());
foreach(var passenger in bus.Passengers)
    Console.WriteLine(passenger);

Example 2

class Bus
{
    public List<Person> Passengers { get; private set; }

    public Bus()
    {
        Passengers = new List<Passenger>();
    }
}

var bus = new Bus();
bus.Passengers.Add(new Passenger());
foreach(var passenger in bus.Passengers)
    Console.WriteLine(passenger);

The first class I would say is better encapsulated. And in this exact case, that might be the better approach (since you should probably make sure it's space left on the bus, etc.). But I guess there might be cases where the second class may be useful as well? Like if the class doesn't really care what happens to that list as long as it has one. What do you think?

5条回答
放我归山
2楼-- · 2019-03-21 16:30

Run your respective examples through FxCop and that should give you a hint about the risks of exposing List<T>

查看更多
该账号已被封号
3楼-- · 2019-03-21 16:34

In most cases I would consider example 2 to be acceptable provided that the underlying type was extensible and/or exposed some form of onAdded/onRemoved events so that your internal class can respond to any changes to the collection.

In this case List<T> isn't suitable as there is no way for the class to know if something has been added. Instead you should use a Collection because the Collection<T> class has several virtual members (Insert,Remove,Set,Clear) that can be overridden and event triggers added to notify the wrapping class.

(You do also have to be aware that users of the class can modify the items in the list/collection without the parent class knowing about it, so make sure that you don't rely on the items being unchanged - unless they are immutable obviously - or you can provide onChanged style events if you need to.)

查看更多
ゆ 、 Hurt°
4楼-- · 2019-03-21 16:48

I would say it all comes down to your situation. I would normally go for option 2 as it is the simplest, unless you have a business reason to add tighter controls to it.

查看更多
做个烂人
5楼-- · 2019-03-21 16:48

Option 2 is the simplest, but that lets other classes to add/remove elements to the collection, which can be dangerous.

I think a good heuristic is to consider what the wrapper methods do. If your AddPassenger (or Remove, or others) method is simply relaying the call to the collection, then I would go for the simpler version. If you have to check the elements before inserting them, then option 1 is basically unavoidable. If you have to keep track of the elements inserted/deleted, you can go either way. With option 2 you have to register events on the collection to get notifications, and with option 1 you have to create wrappers for every operation on the list that you want to use (e.g. if you want Insert as well as Add), so I guess it depends.

查看更多
劳资没心,怎么记你
6楼-- · 2019-03-21 16:53

In example one, it is possible to mutate your collection.

Consider the following:

var passengers = (List<Passenger>)bus.Passengers;

// Now I have control of the list!
passengers.Add(...);
passengers.Remove(...);

To fix this, you might consider something like this:

class Bus
{
  private List<Passenger> passengers;

  // Never expose the original collection
  public IEnumerable<Passenger> Passengers
  {
     get { return passengers.Select(p => p); }  
  }

  // Or expose the original collection as read only
  public ReadOnlyCollection<Passenger> ReadOnlyPassengers
  {
     get { return passengers.AsReadOnly(); }
  }

  public void AddPassenger(Passenger passenger)
  {
     passengers.Add(passenger);
  }
 }
查看更多
登录 后发表回答