[CSHARP-109] Improve type safety of parameters to methods in MongoCollection Created: 21/Nov/10 Updated: 02/Apr/15 Resolved: 06/Dec/10 |
|
| Status: | Closed |
| Project: | C# Driver |
| Component/s: | None |
| Affects Version/s: | 0.9 |
| Fix Version/s: | 0.9 |
| Type: | Improvement | Priority: | Major - P3 |
| Reporter: | Robert Stam | Assignee: | Robert Stam |
| Resolution: | Done | Votes: | 0 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Backwards Compatibility: | Major Change |
| Description |
|
I am probably going to make the following substantial change to the 10gen C# Driver and thought it would be helpful to describe it first and hope to get some feedback. If you look at the MongoCollection class you will find lots of generic methods with type parameters (like TQuery, TIndexKeys, TIndexOptions, TSortBy, etc...). Most of them have no constraints on them so they can be bound to any type at all with no compile time type safety provided. For example: void EnsureIndex<TIndexKeys, TIndexOptions>(TIndexKeys keys, TIndexOptions options) provides no more compile time type safety than: void EnsureIndex(object keys, object options) One consequence of the lack of compile time type safety is that you could accidentally provide the arguments to EnsureIndex in the wrong order and it would compile just fine. The proposed change is to use marker interfaces instead of generic types (I know marker interfaces are sometimes frowned upon but they appear to be justified here, I think). So for example EnsureIndex would be declared: // using marker interfaces: IIndexKeys and IIndexOptions Now we have compile time type safety on both parameters. How will this affect you? If you are using the existing builders to create your argument values not at all, because they will be modified to implement the new marker interfaces. If you are creating a BsonDocument manually you will have to use one of several new subclasses of BsonDocument instead (see the FindAs example below) and in some cases modify your method call to remove the extra type parameters. Another generic method is the FindAs method: TDocument FindAs<TQuery, TDocument>(TQuery query) In this case we aren't concerned about passing arguments in the wrong order, but we can still easily pass the wrong type of argument. For example, the following compiles but is wrong: // note also the awkwardness of having to specify the TQuery type, it can't be inferred We can improve type safety and at the same time simplify the call (by only having to provide the type argument for the result type) if we declare FindAs like: // using the marker interface: IQuery So now the previous FindAs example with a SortBy argument would not compile, and a correct example would look like: var cursor = collection.FindAs<MyDocumentClass>(Query.EQ("x", 1)); Since we sometime prefer to hand build a query using BsonDocument, we would have to do it just a little bit differently: // assume: public class QueryDocument : BsonDocument, IQuery { } }; QueryDocument is just a subclass of BsonDocument that implements the IQuery marker interface. In words: "a QueryDocument is a BsonDocument that we intend to use as a query". Hopefully this wouldn't break too much existing code and would have a considerable benefit by achieving better compile time type safety. Feedback? Thanks, Robert |
| Comments |
| Comment by Robert Stam [ 06/Dec/10 ] |
|
Replaced many unconstrained type parameters with marker interfaces to increase compile time type safety. Note that the unit tests required almost no changes to compile and pass. |
| Comment by Robert Stam [ 21/Nov/10 ] |
|
I forgot to mention that one consequence of this change would be that you could no longer use anonymous classes as queries (or any of the other specification arguments), because anonymous classes would not implement the relevant marker interface. You could still use an anonymous class as the document being saved to the database (with the same existing restriction that you cannot read an anonymous class back from the database). |