简体   繁体   中英

Is it ok to have an array or list returned as a property in .NET?

I was reading some of the documentation on MSDN concerning do's and don't with regards to whether something should be implemented as a property or as a method. I ran into one rule in particular that I have a question about.

If "The operation returns an array" use a method (instead of a property).

The page is here: Choosing Between Properties and Methods

Use a method where the operation returns an array because to preserve the internal array, you would have to return a deep copy of the array, not a reference to the array used by the property. This fact, combined with the fact that developers use properties as though they were fields, can lead to very inefficient code.

I understand that the get method of the property would return a reference to the array, which would allow the array to be changed even if there is no set. In the example they give, they are making a deep copy of the array every time the property is accessed, I guess to avoid the possibility of this happening, and this in turn is very inefficient.

It would not be inefficient if the property just returned the reference, and didn't do all the copying, right? And also using a method instead of a property is not going to automatically protect the list from being modified. It is pretty much the same scenario, you would still need a deep copy.

Is using a property and just returning the reference to the array always bad practice? What if you want the caller to be able to modify the array, or you do not care if they modify it? Is it still bad and why, and if so what would be the proper way to allow the caller to modify?

Can you allow the caller to modify an internal array through a property? Yes, of course, but you will take on a slew of possible issues. How you handle those issues and what you can live with is up to you.

The MSDN advice is correct in a very strict sense. That said I have seen List<T> and T[] properties returned before from classes. If your class is a very simple POCO, this is not a big issue because then those classes are just raw data and there's no real business logic to affect.

That said, if I'm returning a list, and I don't want anyone to mess with the internal list, I either return a deep copy every time, or a ReadOnlyCollection , or an iterator. For example, there's lots of places I cache web service request calls, and when i return a cache item, I do NOT want the caller modifying that data or they'll modify what I'm caching. Thus there I make deep copies (which is still faster than the overhead of the web service call).

You just have to know whether your usage requires the safety or not. Is the class only for internal consumption? Or is it designed to be consumed by a wider audience and you have no idea what they are going to do with it? Those type of questions may drive your response.

Sorry for a "it depends" answer, but it truly does depend on what your goal is and if the internals of the class are sensitive to change.

UPDATE You can also return an iterator, I'd avoid returning IEnumerable as a superclass up-cast because it can be cast back down, but if you return an iterator instead (like using Skip(0)) you are safe (aside from still being able to modify the contained objects of course).

For example:

public IEnumerable<T> SomeList 
{
    get { return _internalList.Skip(0); }
}

Is better than:

public IEnumerable<T> SomeList
{
    get { return _internalList; }
}

Because the later can still be cast back to List<T> or whatever it was, while the first is an iterator and can't be modified.

If your class's internal state does not depend on the array or list you expose, it is ok to return it as-is. So it should not be possible to sabotage your class by changing the list or array.

If you trust the calling code (ie It is written by you), it could be also ok if it gains you performance (measurable and relevant to your app).

If you want to give readonly access, you can return a ReadonlyCollection that wraps around the list or array you want to expose.

It's pretty much always a bad thing to do but it doesn't stop people doing it (me included). It's a matter of using a suitable case for the usage scenario.

However, the simplest thing to do to protect the array's encapsulation is to expose only an IEnumerable or ICollection and the methods to mutate the internal array. See the following example. I've used List here as the semantics are simpler.

class YourClass {
    List<string> list = new List<string>();
    public IEnumerable<string> List {
        get { return list; }
    }
    public void Add(string str) {
        list.Add(str);
    }
}

As the return type is IEnumerable/ICollection then there is no possibility to mutate the internal state other than via the methods/properties on the class.

The ultimate encapsulation is to copy the entire collection on property access but this is inefficient for most cases.

Semantically speaking, things without much cost which are idempotent and efficient should be assigned a property. Things that mutate data or have a high execution cost should be a method.

You're correct in your assumption that if you merely return an existing array from a property get, rather than perform a copy, it's no longer a question of efficiency, since doing that is as efficient as returning any other reference type.

As for it being a "bad practice", that largely stems from the reason they describe: that even if you only provide a get accessor on your property, a user can still modify the contents of the array, and thereby mutate the state of your class without your class having a way of validating the changes -- and, worst of all, this consequence of the design is not very obvious unless you already have a deep enough knowledge of C# array semantics.

If you can accept that a user will be able to mutate the contents of the array, and if you can accept that your class will have no recourse to validate or prevent those changes, then returning the array reference is probably acceptable, just be sure to document that decision and the rationale behind it.

However, unless you really need the performance of a raw array, you're probably still better off having your property return a collection class instance instead. Consumers of your class will still be able to reference items by index using identical syntax, but you retain the capability of making the returned collection immutable, or for providing on-update validation.

Returning an array or a collection in general using a property is not a bad practice I would say. Especially if the same cached instance is returned every time. However if a new instance is created every time, then a method called something like CreateItems() would make this fact clearer.

I disagree with the premise that you would want to preserve the internal array, you would have to return a deep copy of the array, not a reference to the array used by the property.

In fact it would be quite "astonishing" if a property did return a deep copy.

It is sometimes the case that you do want to preserve the internal structure and just return a read-only view of it (in the form of an IEnumerable<T> ) but that's orthogonal to properties.

They make the case that to the outside world, properties should act like fields, and a field would allow modification. If they're saying that it's bad practice have arrays/lists as fields, that's an entirely different matter unrelated to properties.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM