C# Special Method Names

Updated on November 04 2024 with the dictionary syntax

Introduction

You may not have realized that some of the patterns that you’ve been using for ages in your C# programs are based on conventions, rather than on a specific API. What I mean by this is, some constructs in C# are based on some magical methods with well-defined names which are not defined in any base class or interface, but yet just work. Let’s see what they are.

Enumerations

You are probably used to iterating through collections using the foreach statement. If you are, you may know that foreach actually wraps a call to a method called GetEnumerator, like the one that is defined by the IEnumerable and IEnumerable<T> interfaces. Thus, you might think, the magic occurs because the collection implements one or the two, but you would be wrong: it turns out that in order to iterate through a class using foreach all it takes is that the class exposes a public method called GetEnumerator that returns either a IEnumerator or a IEnumerator<T> instance. For example, this works:

public class Enumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
}

var e = new Enumerable();

foreach (int i in e) { /*...*/ }

As you see, there is no need to implement any of these interfaces, but it is a good practice, for example, because it gives you access to LINQ extension methods.

As of C# 9, the compiler can even find an GetEnumerator implementation from extension methods:

public class EnumerableClass
{
    //some fields
}

public static class EnumerableClassExtensions
{
    public static IEnumerator<int> GetEnumerator(this EnumerableClass obj)
    {
        foreach (/* some condition */)
        {
            yield return someValue;
        }
    ]
}

The specification can be found here.

Deconstruction to Tuples

Tuples were introduce in C# 7. In a nutshell, they provide a way for us to return multiple values from a method:

(int x, int y) GetPosition()
{
    return (x: 10, y: 20);
}

Another option is to have a class deconstructed into a tuple. Say, for example, that we have a class like this:

public class Rectangle
{
    public int Height { get; set; }
    public int Width { get; set; }
}

We can have it deconstructed into a tuple, by providing one or more Deconstruct methods in this class:

public void Deconstruct(out int h, out int w)
{
    h = this.Height;
    w = this.Width;
}

Which allows you to write code like this:

var rectangle = new Rectangle { Height = 10, Width = 20 };
var (h, w) = rectangle;

You can implement multiple Deconstruct methods with different parameters, which must always be out. When you try to assign your class to a tuple, C# will try to find a Deconstruct method that matches the tuple’s declaration, or throw an exception if one cannot be found:

public void Deconstruct(out int perimeter, out int area, out bool square)
{
    perimeter = this.Width * 2 + this.Height * 2;
    area = this.Width * this.Height;
    square = this.Width == this.Height;
}

var (perimeter, area, square) = rectangle;

Collection Initialization

Since C# 6, we have a more concise syntax for initializing collections:

var strings = new List<string> { "A", "B", "C" };

The syntax to follow is an enumeration of items whose type matches the collection’s item type, inside curly braces, each separated by a comma. This is possible because there is a public Add method that takes a parameter of the appropriate type. What happens behind the scene is that the Add method is called multiple times, one for each item inside the curly braces. Meaning, this works too:

public class Collection : IEnumerable
{
    public IEnumerator GetEnumerator() => /* ... */
    public void Add(string s) { /* ... */ }
}

Notice that this collection offers a public Add method and needs to implement either IEnumerable or IEnumerable<T>, which, mind you, do not define an Add method. Having this, we can write:

var col = new Collection { "A", "B", "C" };

The magical Add method can have multiple parameters, like for dictionaries (IDictionaryIDictionary<TKey, TValue> or any class implementing it) , or any other method whose Add method takes two parameters:

var dict = new Dictionary<string, int> { { "A", 1 }, { "B", 2 }, { "C", 3 } };

Each parameter will need to go inside it’s own set of curly braces.

What’s even funnier is, you can mix different Add method overloads with different parameters, the right one will be called depending on the current value:

public void Add(int i) { /* ... */ }
public void Add(string s) { /* ... */ }

var col = new Collection { 1, 2, 3, "a", "b", "c" };

Dictionaries

Dictionaries, being a collection, can benefit from the collection syntax, as I've shown, but they also support a new special syntax:

var dict = new Dictionary<string, int>
{
    ["A"] = 1,
    ["B"] = 2,
    ["C"] = 3
};

Whichever you prefer is really up to you, they do exactly the same, this one is probably neater for dictionaries.

Conclusion

In this post I introduced the magical GetEnumerator, Deconstruct and Add methods plus the special syntax for dictionaries. This was just for fun, the information here is probably useless, but, hey, it’s done! Winking smile

                             

2 Comments

  • Another magic method is GetAwaiter.

    Support for LINQ-syntax expressions can be implemented similarly.

    These can be added to types as extension methods. I'm not sure if this applies to all of them.

  • Hi, Robert!
    You're probably right, that's what await does underneath. Thanks!

Add a Comment

As it will appear on the website

Not displayed

Your website