-
Pattern-based Index/Range translation
-
Default interface implementations: Is object.MemberwiseClone() accessible in an interface?
The current design for Index has a high implementation overhead for
types that currently have int
indexers and all of the implementations
sum to delegation to the int
indexer. Also, full support can only be
added by type authors because there are no "extension indexers" in the
language. There's also a small overhead for using the Index type, as
opposed to doing a "direct" translation into the int
indexer.
Range is similar to the previous.
Proposal: contextual conversions for Index and Range members
In short, there would be a new contextual language-defined conversion
from Index
to int
for any instance member where the receiver is
"indexable".
One significant limitation is the conversion could be more applicable than expected, meaning that a member could be called with an Index that takes an int, but the member does not intend to be called with an Index, e.g. a member that takes a length, not a index, could be called with a System.Index type due to the containing type being "indexable".
The "contextual" conversion also seems very complicated, especially given that C# is already very complicated.
*Q: Why only support extensions only up to the type already being "indexable"? Could we add support for an extension Count() or similar?
A: Adding general collection functionality is broader than just a different form of indexing. That seems like an instance of a more general problem in C#. There are other proposals, like roles, that provide the general purpose extensibility needed for the above.
Q: What if Count is implemented slowly or improperly?
A: This is a violation of the framework design guidelines. Count should be cheap and O(1). We're willing to double down on this guideline.
Range
Note: the Slice method is preferred to the Range indexer if the Slice is present.
This does violate the general principle we have that exact type matches are always preferred over anything else and it makes adding a Slice method a breaking library change, since it will be preferred over the Index indexer. It would also allow a library to add an extension Slice to override the type's implementation, which is generally not desired.
The general argument of recognizing Slice seems useful and allows people to add extension support for slicing, which we like.
Note: multi-dimensional arrays are not supported for either
We like the general approach of the prior proposal, but think adding contextual conversions is a step too far. Contextual conversions would be both difficult for the implementation and for the user to understand.
For Index, if a type is "indexable" (has a Count or Length, and an indexer
that takes an int
) then a synthetic indexer will be added to the type that
takes an Index and implements a translation to the indexer.
For Range, we do the same thing, but for "rangeable types". A type is "rangeable" if it has a Count or a Length, and a Slice(int,int) method.
Q: Is the 'indexable' pattern based on the constructed type or the original definition?
Unknown, revisit later.
Q: Do we want to support Slice(Range) or Slice(int)?
Probably not -- not worth the complexity.
Note: for the counter-proposal, the length should only be evaluated once, and after the arguments are evaluated.
Warning or error for writing your own Range indexer?
Conclusion
We think the first proposal went too far, but think the alternative is workable. Let's try to flesh out more of the details of that proposal and then consider it again for inclusion in C# 8.
There are two things we'd like to consider: potential uses for the feature and consistency with the rest of the language rules.
Example of use case:
interface IPoint
{
public int X { get; protected set;}
public int Y { get; protected set;}
public IPoint WithX(int x)
{
var tmp = MemberwiseClone();
tmp.X = x;
return tmp;
}
}
One argument also levelled against the feature is that it may be unexpected that an interface could call MemberwiseClone(). A counter argument is that it may be unexpected if a base class calls MemberwiseClone, but that is the current behavior in classes.
On the other hand, many people may have an impression that interfaces and class are essentially different type hierarchies and they meet at the implementation. There is also another protected member, the destructor, which is on object and is questionable to provide to the interface itself.
Conclusion
The only thing we strongly agree upon is that many of the members of object should not have been there in the first place. Beyond that, we aren't particularly swayed by either the advantages of having the access or the dangers of misuse. We'll slightly prefer our earlier decision: protected members of object are not accessible in interfaces.