Although C# as a language has evolved tremendously since its 1.1 incarnation,
the CLR bytecode that applications are compiled to has stayed relatively
the same. New features like the yield keyword, lambdas, and async are
simply transformed into the “old” way of coding the feature (for example,
yield is rewritten to a switch-based state machine). Even default parameters
are implemented internally by Attributes (which is why default values are
limited to compile time constants).
A number of these new features are triggered off of what I call Magic Methods: syntactic sugar that, in the end, is transformed into a call to a specific method defined on the class. Since there are built in types integrated with most of the features, a developer might never have to implement any of the methods herself, but they’re still an interesting look into the depths of C#.
Foreach Loop
What’s so special about the foreach loop, anyway? Isn’t it just a thing you
call with an IEnumerable? Close, but not quite. The compiler actually takes
the concept “object oriented” and throws it out the window. To be the target of
a foreach loop, the object must follow two rules:
- Define a public method called
GetEnumeratorwith no parameters. - The return type is any type that defines a public
Currentproperty and a public methodMoveNextthat returns a boolean.
Of course, these are exactly (no more, no less) what the interfaces for
IEnumerable and IEnumerator look like, but the interesting part is that
there is no requirement to use either of those interfaces. For example, this
code prints a pyramid of . with no interfaces in sight!
static void Main()
{
foreach(var i in new SomeLoop())
{
Console.WriteLine(i);
}
}
public class SomeLoop
{
public Iterator GetEnumerator()
{
return new Iterator();
}
}
public class Iterator
{
public string Current { get; set; }
public Iterator()
{
Current = "";
}
public bool MoveNext()
{
if(Current.Length == 10) return false;
Current += ":";
return true;
}
}
Collection Initializers
You’ve probably already seen these in action on existing collections, but there is nothing stopping you from implementing this feature on your own classes! I’m referring to this handy bit of syntax for initializing Lists, arrays, Dictionaries, and various other collections:
var c = new List<string> { "hello", "world" };
The compiler translates this into three statements, so long as the object
(List<T> in this case) defines an Add method and implements IEnumerable:
var c = new List<string>();
c.Add("hello");
c.Add("world");
Pretty simple stuff, but there is one interesting thing you can do with it:
make Add a generic method. The example below “transforms” the collection
initializer inputs into a list of types:
static void Main()
{
var c = new Stuff
{
1, "hello"
};
foreach(var type in c)
{
Console.WriteLine(type);
}
}
public class Stuff : IEnumerable
{
private List<Type> Types = new List<Type>();
public void Add<T>(T value)
{
Types.Add(typeof(T));
}
public IEnumerator GetEnumerator()
{
return Types.GetEnumerator();
}
}
Prints:
System.Int32
System.String
Also worth noting is that the Add method can take multiple parameters (even
default parameters!), commonly seen when initializing Dictionaries
(new Dictionary<string, string>{"key", "value"}).
LINQ Query Expressions
That SQL-like syntax that debuted alongside LINQ works exactly like you’d
expect: it’s transformed into the method syntax (.Where().Select() etc.)
before being compiled. I can’t imagine many cases when this knowledge could
be useful, but there may be some potential when designing a “fluent interface”
to redefine the traditional LINQ operators. The following sample redefines
Where to be completely random, ignoring the comparison provided.
static void Main()
{
var c = (from x in new[]{1, 2, 3, 4, 5}
where x > 2
select x);
foreach(var m in c)
{
Console.WriteLine(m);
}
}
public static class MyExtensions
{
private static System.Random rand = new System.Random();
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (TSource item in source)
{
if (rand.Next(0, 2) == 0)
{
yield return item;
}
}
}
}
await Anything
await support is triggered by a method on an object (or an extension method)
called GetAwaiter. That returned object needs to implement
INotifyCompletion as well as define a boolean
IsCompleted property plus a GetResult method that determines the “awaited”
return type.
In the interest of alternative approaches, here’s a simple (read overly
complicated) way to turn an integer into a string, the async way!
static void Main()
{
IntToString(300);
}
// Unfortunately, the return value from an async function is strictly
// limited to void, Task, or Task<T> as far as I know, there's no way to
// magic yourself a custom type to return.
public async void IntToString(int intVal)
{
var myNewString = await intVal;
Console.WriteLine(myNewString);
}
public static class AwaiterExtensions
{
public static Awaiter<int> GetAwaiter(this int val)
{
return new Awaiter<int>(val);
}
}
public class Awaiter<T> : INotifyCompletion
{
private string value;
public Awaiter(T val)
{
value = val.ToString();
}
public bool IsCompleted { get { return true; } }
public void OnCompleted(Action func)
{
}
public string GetResult()
{
return value;
}
}