The SLAR on System.CharEnumerator


Continuing in the series on sharing some of the information in the .NET Framework Standard Library Annotated Reference Vol 1 here is an annotation from the System.CharEnumerator class. 

 

BA – Our reason for adding this class to the BCL was not that we felt there was a

huge demand for enumerating characters. It was required to enable efficient support

for applying foreach over strings. Simply using IEnumerator would have caused

a boxing operation for each character accessed out of the string.

string s = “John Smith”;

foreach (char c in s) {

Console.WriteLine (“{0}-”, c);

}

A side note on this is that the C# compiler now does not even use this class. It special cases

foreach over strings and generates very efficient code for accessing a string

that not only avoids the boxing operation, but is also as good as you could do with a

for loop. In particular it does not pay the cost of another allocation for the enumerator,

and takes full advantage of JIT optimizations.

This is a classic example of why explicit method implementation is such an important

feature. Notice that we have both:

object IEnumerator.Current { get; }

public char Current { get; }

We needed to have Current property return Object to satisfy the IEnumerator

interface. But we also wanted to have a Current property that returned a char such

that usage of this type would not force a boxing operation for each access and to make

usage easier (no ugly casts). However in C# (and the CLS) it is not possible to differ

only by return type. So we explicitly implement the Object version such that the

property is only there when cast to the interface.

 

Brian Grunkemeyer – The key point to understand about private interface implementation (using the C#

terminology although it is more properly a subset of the CLI feature we call methodimpls)

is that it allows you to override an interface method based on its return type.

While it also allows you to provide different method implementations for two different

versions of a method coming from different interfaces, in practice most people try to avoid

problems like this. Multiple inheritance of interfaces, but not classes, seems to make the

classic diamond inheritance pattern rarer, or at least significantly less confusing.

 

Comments (3)

  1. Ken says:

    What is the reasoning behind not supporting covariant return types? If that was the case, you could simply write:

    public char Current { get; }

    which would satisfy the implementation of IEnumerator.Current, since "char" ISA "object". It’s always safe to get more specific on a return type (value types complicate things, but certainly doesn’t make it impossible).

    Besides being easier to maintain, clearer to read, and just plain more accurate to what was meant, there are situations where the explicit interface implementation approximation of covariant return falls apart – whenever you have object inheritance. To wit:

    If covariant return was supported:

    interface Clonable {

    Cloneable Clone();

    }

    class Foo : Cloneable {

    public Foo Clone();

    }

    class Bar : Foo {

    public Bar Clone();

    }

    Without covariant return:

    interface Cloneable {

    Cloneable Clone();

    }

    class Foo : Cloneable {

    public Foo Clone();

    Cloneable Cloneable.Clone();

    }

    class Bar : Foo {

    //public Bar Clone(); // not legal!

    public Foo Clone(); // have to settle for this

    }

  2. Neyah says:

    I was under the impression that the CLS supported overloading on return types, but that none of the languages which target the CLS support this.

    The help files for the Dotfuscator application that comes with Visual Studio indicate that the "Pro" version has an option to turn on "Enhanced Overload Induction" for renaming of functions to overload on return types. Another benefit that it lists for this is that it futher hinders decompiling since none of the target languages support this.

    This brings up the question of why none of the .NET languages support this if the CLS supports it.

  3. Ken says:

    Neyah –

    I have not looked into exactly what the CLS supports in terms of overloading that is not language supported, but covariant return really isn’t about overloading. Overloading on return type would be:

    class Foo {

    public Bar Overload();

    public Baz Overload();

    }

    That is, two distinct functions with different signatures in the same scope. Since C# and similar languages don’t really have a way of specifying return type in a call, this would be confusing and (with conversions in play) probably error prone.

    Covariant return types, on the other hand, are about overrides, not overloads.

    class Base {

    public virtual BaseReturn Foo();

    }

    class Derived {

    public override DerivedReturn Foo();

    }

    In this case, both Base.Foo() and Derived.Foo() describe the same (in spirit) function. They effectively have the same signature, since Derived.Foo() can safely be used in any situation Base.Foo() can, however when the compiler knows you are working with a Derived object, it can be more specific with the return type of Foo. This results in less casting, fewer errors, and all around more readable code.