In C#, I started seeing all these magic methods popping up, without being backed up by an interface. Why was this chosen?
Let me explain.
Previously in C#, if an object implemented the IEnumerable
interface, it would automatically be iterable by a foreach
loop. That makes sense to me, since it's backed up by an interface, and if I were to have my own Iterator
function inside the class being iterated through, I could do that without worrying that it would magically mean something else.
Now, apparently, (not sure when), these interfaces are no longer required. It just needs to have the right naming conversions.
Another example is making any object awaitable by having a method named exactly GetAwaiter
which has a few specific properties.
Why not make an interface like they did with IEnumerable
or INotifyPropertyChanged
to back this "magic" up statically?
More details on what I mean here:
http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/
What are the pros and cons of magic methods, and is there anywhere online where I can find anything on why these decisions were made?
1 Answer 1
In general "magic methods" are used when it's not possible to create an interface that would work the same way.
When foreach
was first introduced in C# 1.0 (that behavior is certainly nothing recent), it had to use magic methods, because there were no generics. The options basically were:
Use the non-generic
IEnumerable
andIEnumerator
that work withobject
s, which means boxing value types. Since iterating something like a list ofint
s should be very fast and certainly shouldn't create lots of garbage boxed values, this is not a good choice.Wait for generics. This would probably mean delaying .Net 1.0 (or at least
foreach
) by more than 3 years.Use magic methods.
So, they chose option #3 and it stayed with us for backwards compatibility reasons, even though since .Net 2.0, requiring IEnumerable<T>
would have worked too.
Collection initializers can look different on each collection type. Compare List<T>
:
public void Add(T item)
and Dictionary<TKey, TValue>
:
public void Add(TKey key, TValue value)
You can't have a single interface that would support only the first form for List<T>
and only the second for Dictionary<TKey, TValue>
.
LINQ methods are usually implemented as extension methods (so that there can be just a single implementation of e.g. LINQ to Object for all types that implement IEnumerable<T>
), which means it's not possible to use an interface.
For await
, The GetResult()
method can return either void
or some type T
. Again, you can't have a single interface that can handle both. Though await
is partially interface-based: the awaiter has to implement INotifyCompletion
and can also implement ICriticalNotifyCompletion
.
-
1Are you certain "they chose option #3" for C# 1.0? My recollection is it was option 1. My memory is, C# claimed significant superiority over Java because the C# compiler did 'auto-boxing' and 'auto-unboxing'. It was claimed, C# made code like that foreach work much better than Java partly because of that.gbulmer– gbulmer08/13/2014 22:07:57Commented Aug 13, 2014 at 22:07
-
1The documentation for
foreach
in C# 1.2 (there is nothing on MSDN for C# 1.0) says that the expression inforeach
"Evaluates to a type that implementsIEnumerable
or a type that declares aGetEnumerator
method." And I'm not sure what you mean by that, unboxing in C# is always explicit.svick– svick08/13/2014 22:15:17Commented Aug 13, 2014 at 22:15 -
1@gbulmer And the ECMA specification for C# from December 2001 (which has to mean it's for C# 1.0) also says that (§15.8.4): "A type C is said to be a collection type if it implements the System.IEnumerable interface or implements the collection pattern by meeting all of the following criteria [...]".svick– svick08/13/2014 22:21:01Commented Aug 13, 2014 at 22:21
-
1@svick
foreach(T x in sequence)
applies explicit casts toT
on the elements of the sequence. So if sequence is a plainIEnumerable
andT
is a value type it will unbox without any explicit cast being written in your code. One of the uglier parts of C#.CodesInChaos– CodesInChaos08/14/2014 10:54:32Commented Aug 14, 2014 at 10:54 -
@CodesInChaos "Auto-unboxing" sounds pretty general, I didn't realize it referenced this specific feature. (I guess I should have, since we were talking about
foreach
.)svick– svick08/15/2014 00:00:56Commented Aug 15, 2014 at 0:00
async
/await
, then that will only work with code that was written after .NET 4.5 became wide-spread enough to be a viable target ... which is basically now. But a purely syntactic translation into method calls allows me to addawait
functionality to existing types after the fact.foreach
loop back in the beginning. There never has been a requirement for the object to implementIEnumerable
forforeach
to work. It's just been convention to do so.