A variation on Marc Gravell's answer:
public static void Log(IReadOnlyList<object> objects)
{
foreach (var obj in objects)
{
Console.WriteLine(obj);
}
}
This doesn't add anything to this particular example, but if you wanted to use indexed access to the collection like you could with a List<T>
, this lets you do that in a way that IEnumerable<object>
doesn't. (And yes, there's a LINQ operator for indexed access of enumerables, but it's clumsy, and may also be horribly slow. IReadOnlyList<object>
is useful if you want to make it clear that your method requires efficient indexed access.)
Like Marc's version that uses IEnumerable<object
> as the argument type, this exploits covariance - unlike List<T>
, T
is covariant in IEnumerable<T>
and in IReadOnlyList<T>
. That means that because string
is an object
, an IEnumerable<string>
is an IEnumerable<object>
, and likewise, IReadOnlyList<string>
is an IReadOnlyList<object>
.
The read-only nature of both interfaces is important. The whole reason your original example fails is that List<T>
supports both reading and writing - if you pass me a List<object>
I can add anything to it - a string
, a Giraffe
or whatever I like. That's why a List<string>
is not an acceptable substitute for a List<object>
- I can't add a Giraffe
to a List<string>
, even though I can add one to a List<object>
. But because IEnumerable<T>
and IReadOnlyList<T>
do not allow objects to be added to the collections they represent, all that matter is what you can get out, not what you can put in. Anything that comes out of a collection that contains only string
objects will be an object
because everything's an object
.
And yes, I know your original code didn't attempt to add anything to the list, but that doesn't matter - all C# cares about in this case is how the function signature looks. And by specifying IReadOnlyList<object>
you are making clear that you will never attempt to modify the list, at which point C# knows it's OK to pass the List<string>
.