Reputation: 21
I am writing a library and I'm trying to determine the best type to use for method parameters to a method. I'm also trying to determine where to draw the line between being too picky and getting the write method definition.
Here is some code
public void MethodTakesIList(IList<MyClass> myClassInstances) {
List<List<byte>> byteLists = GetByteLists(myClassInstances.Count);
for (int i = 0; i < myClassInstances.Count; i++) {
ProcessBytesAndMyClassInstance(byteLists[i], myClassInstances[i]);
}
}
public void MethodTakesIEnumerable(IEnumerable<MyClass> myClassInstances) {
List<List<byte>> byteLists = GetByteLists(myClassInstances.Count());
int i = 0;
foreach(MyClass instance in myClassInstances) {
ProcessBytesAndMyClassInstance(byteLists[i], instance);
i++;
}
}
In this article it states that I "should always use that type that provides a contract for only the methods you really use." The method that takes an IEnumerable still defines an index which is used in the loop. Since this index would point to a valid entry if myClassInstances were a list does that mean I should just use the method that accepts an IList? I would now have the capability to use the indexer of the IList. Or since there exists a solution that accepts IEnumerable should I use that one since it supports the greatest number of inputs?
Upvotes: 1
Views: 1009
Reputation: 117175
If I were you I would take inspiration from how Microsoft implemented LINQ. Many of the operators that could benefit from being able to use a collection type still have IEnumerable<TSource>
as the source data, but they simply try to cast to ICollection<TSource>
to get direct access to indexing and other properties.
Take (this slightly modified) Count
, for example:
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return collection.Count;
}
ICollection collection2 = source as ICollection;
if (collection2 != null)
{
return collection2.Count;
}
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
num = checked(num + 1);
}
return num;
}
}
If the input type is castable to a collection then the Count
operator simply returns the count; otherwise it iterates through the list.
That way you get the best of both types.
Upvotes: 1
Reputation: 30165
IEnumerable
is generally a safe bet if you are unsure how people will use the API. There are plenty of cases when a user will not have a IList
object, especially when using things from Linq, in addition to any other collections other than lists, such as LinkedList
or Queue
.
For example if they do someList.Select((x) => new OtherThing(x, "test"))
, filter with Where
, etc., then they will end up with an IEnumerable
. While there is also in System.Linq
a convenient myEnumerable.ToList()
it is an extra conversion they would have to do and has some performance impact, maybe worse than the one you hoped to avoid.
Of course, there are times when for performance reasons other types are preferable, especially when dealing with large amounts of small values (e.g. byte[]
), or making your implementation simpler, for example for random access or modifying the data.
You have to consider the trade offs yourself, although you might take inspiration from the standard classes and other popular libraries for the language. For public libraries you also have to be very careful in considering binary compatibility, for example if you return an IEnumerable
then you can change your implementation to use a different actual type, if you return a List
or even IList
then you are more limited in what is technically possible without breaking compatibility.
Upvotes: 1