.NET Internals Cookbook Part 4 — Type members

This is the fourth part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents

18. Can you add type constructor to the interface?

In C# no. In IL — yes. See the code below:

If you are not familiar with IL then just follow the comments and this is the part you should be looking for:

We have an interface here and a method named cctor. There are special names in IL, constructors are named ctor whereas type constructors (or type initializers) are considered a class constructors hence the name cctor.

However, it is not very useful as the constructor is not called. You can specify type constructor for structs as well, but it may be ignored in some situations. For instance, “The creation of default values (§18.3.4) of struct types does not trigger the static constructor” (from the C# standard).

19. How to change the name of the indexer?

Using this code:

When you decompile it, you can see that it calls:

Why would you do that? If you use language without direct support for indexers (you do remember there are over 20 languages running on CLR, don’t you?), you need to call them by name.

20. Can you have static fields and methods in the interface?

Yes, you can. See this code:

C# doesn’t support it, but in IL you can define static field, method or type constructor.

21. How many objects are created in new Foo {Bar = 1}?

One. The property is then assigned by calling the setter.

See this code

When you decompile it you get this:

Clearly, there is one object and a setter call.

22. What is covariance? Contravariance? How does it work with arrays and with generics?

This allows you to return or accept subtype when base type is expected. This short explanation actually has a lot of intricate details which you may have just ignored.

First, type and class are not the same. Type specifies requirements imposed on an object whereas class is a concrete implementation of those. In C# we have classes only but in other languages you can have types without classes or object-oriented programming without classes at all. Oddly enough, JS is not considered an object-oriented language even though it is based on objects everywhere whereas C# is considered OOP but it is based on classes (maybe we should call it “class-oriented language”? Let the flamewar begin!).

This also means that we can have a subtype and not a subclass at the same time or a subclass which is not a subtype! Technically we can consider nominal subtyping, structural subtyping, subtyping without subclassing and many other combinations. I highly recommend reading A Theory of Objects which explains it in depth. Also, playing with JS or Scala can be beneficial as those languages have very interesting type systems.

Let’s get back to C#. Imagine there is a class Base and a class Derived : Base. We can consider IEnumerable< Derived> being an instance of IEnumerable< Base> because Derived is a subtype of Base. Thanks to that we can assign IEnumerable< Derived> to IEnumerable< Base> and it works. Typically we can use it when returning values from methods — we often say that “return value” is in a “covariant position” which is true for most of the times.

The same goes for input parameters: we can create a delegate of type Action< Base> and assign to it an instance of Action< Derived> and it does work. We say that “input parameter” is in a “contravariant position” (once again, true for most of the times).

Covariance is supported in arrays, delegates (also called “method group variance”) and generic type parameters in interfaces and delegates. It is marked using out keyword in C#. Contravariance is supported in delegates and generic type parameters in interfaces and delegates. It is marked with in keyword.

Covariance in arrays is dangerous. It is supported because Java had it before C# emerged and then C# just copied it. But it can easily break your application:

Output:

You use an array of strings as an array of objects. This is fine unless you assign a non-string to its content. If you do — you get an exception in runtime. Fortunately, most of the times compiler can find those subtle bugs when we use generic types.

When it comes to input parameters, compiler can figure it out with contravariant positions:

Output:

We have a lambda accepting strings and printing them out and then we want to use it as a lambda accepting objects. This clearly can’t work as we would be allowed to pass object as a string parameter. So generally input position is contravariant whereas output position is covariant.

But things get really tricky when you mix covariant and contravariant positions:

We see that with higher order function we can reverse the relationship of subtypes!

See also this great series by Eric Lippert explaining how things get very tricky with higher order functions and this great post by Tomasz Sitarek (polish language only) to see the difference between a type and a class, and this Alexandra Rusina’s post showing multiple examples.

23. Can you use extension methods with dynamic?

No. See this code:

This is because extension methods are compiled as static ones. This code in fact does that:

In C# you could write it as:

However, DLR looks for methods of the object so you cannot use extensions with dynamic code.

As a side note, this actually explains why creating traits using just extension methods is not very useful (unless we implement polymorphism manually. That’s because when you call a method the call is bound statically so you have no dynamic dispatch which is the whole point of trait mechanism.

Yet another side note, Java introduced default methods in interfaces which can be used to build very basic traits. They do support polymorphism (so they are dynamically bound) but you cannot call base method (super in Java nomenclature) out of the box, you need to specify the superclass (base implementation) explicitly. Hopefully C# gets this right (or we can always implement polymorphism manually).

24. What is the difference between Equals and ==?

Equals is bound in runtime, == is bound in compile time. See this:

Output:

You can see that Equals uses actual object types whereas == is bound statically. If you decompile the code you get:

You can see that for non-overloaded == operator it uses ceq instruction.

25. How to write parameterless constructor for a struct?

Once again, you can’t do that in C# but you can in IL.

C# 6.0 was supposed to introduce a way of implementing parameterless constructors but it was removed because of a bug in Activator.CreateInstance

See also this John Skeet’s post.

26. What is the difference between new Struct() and default(Struct)?

The former calls parameterless constructor, the latter creates new instance and with all fields zeroed out. Most likely this doesn’t make a difference for you.