1

The following is a quote from Microsoft's framework design guidelines:

Collection Properties and Return Values

X DO NOT provide settable collection properties.

Users can replace the contents of the collection by clearing the collection first and then adding the new contents. If replacing the whole collection is a common scenario, consider providing the AddRange method on the collection.

I agree with the guideline - however I feel it combines badly with the C# initialization syntax.

public class RequestParameter
{
    Collection<string> FilesToDownload { get; } = new Collection<string>();
    Collection<string> FilesToRemove { get; } = new Collection<string>();
}

This prevents the API user from doing anything like:

client.SendRequest(new RequestParameter
{
    FilesToDownload = { itemsToDownload.Select( item => item.path ) },
    FilesToRemove = { GetFilesToRemove() },
});

There are four alternatives I've came up with:

Constructors with named parameters

The constructor would need to define all type fields as parameters as I don't want to make it a common case that the users would need to mix constructor parameters and initializer syntax.

This makes the constructors not backwards compatible if we need to add new fields to the type. This is an API on top of a JSON-based API where I want to reserve the ability to add additional fields to the types without breaking our clients.

Settable properties

Disregard the "no setter" guideline would be an "easy" way out.

Add an Add(IEnumerable<T>) method on the collections

Pragmatically this would allow the syntax I want for the initializers. However it kind of breaks the usual expectations of Add vs AddRange on collection types.

Add a settable IEnumerable<T> Values property on the collections

The initializer syntax would change to FilesToRemove = { Values = myItems }. Again the purist in me kind of dislikes this extra property just for the sake of allowing the initializer syntax to be used to define the whole collection.


Are there any other alternatives? An ideal way would be something akin to operator= overloading in the initializer syntax to allow Foo = bar to invoke a custom function that could do copying instead of initializing the whole collection but as far as I know, C#/.Net framework doesn't support anything like this.

This same issue also applies to object-initializers in a way. The API I'm working with requires certain JSON fields to have a value and not be null. In the .Net API I wanted to modeled these properties as read-only properties with default value:

public ItemClass Item { get; } = new ItemClass();

This would have made it clear to the user which properties can be null and which can't but it prevented the user from setting these using convenience constructors:

new Item
{
    Item1 = ItemClass.FileItem( "storage", "path" ),
    Item2 = ItemClass.UrlItem( "url" ),
}

1 Answer 1

2

You may be over thinking this.

If

new RequestParameter
{
    FilesToDownload = ...,
    FilesToRemove = ...
}

is a construction pattern you expect to be common, why not provide a public constructor overload, e.g.,

public class RequestParameter
{
    public RequestParameter(Collection<string> filesToDownload, Collection<string> filesToRemove)
    {
        FilesToDownload = filesToDownload;
        FilesToRemove = filesToRemove;
    }

    public RequestParameter()
        : this(new Collection<string>(), new Collection<string>())
    {
    }

    public Collection<string> FilesToDownload { get; protected set; }
    public Collection<string> FilesToRemove { get; protected set; }
}

?

1
  • I can't add new parameters to a constructor when the types gain more fields. Instead I'd need to add new constructor overloads. I brought up this approach in the "Constructor with named parameters" alternative. There might be several fields on the types with various of them being optional. Nov 15, 2016 at 14:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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